1//////////////////////////////////////////////////////////////////////
   2// LibFile: attachments.scad
   3//   The modules in this file allows you to attach one object to another by making one object the child of another object.
   4//   You can place the child object in relation to its parent object and control the position and orientation
   5//   relative to the parent.  The modifiers allow you to treat children in ways different from simple union, such
   6//   as differencing them from the parent, or changing their color.  Attachment only works when the parent and child
   7//   are both written to support attachment.  Also included in this file  are the tools to make your own "attachable" objects.
   8// Includes:
   9//   include <BOSL2/std.scad>
  10// FileGroup: Basic Modeling
  11// FileSummary: Positioning objects on or relative to other objects.  Making your own objects support attachment.
  12// FileFootnotes: STD=Included in std.scad
  13//////////////////////////////////////////////////////////////////////
  14
  15
  16// Default values for attachment code.
  17$tags=undef;      // for backward compatibility
  18$tag = "";
  19$tag_prefix = "";
  20$overlap = 0;
  21$color = "default";
  22$save_color = undef;         // Saved color to revert back for children
  23
  24$anchor_override = undef;
  25$attach_to = undef;
  26$attach_anchor = [CENTER, CENTER, UP, 0];
  27$attach_norot = false;
  28
  29$parent_anchor = BOTTOM;
  30$parent_spin = 0;
  31$parent_orient = UP;
  32
  33$parent_size = undef;
  34$parent_geom = undef;
  35
  36$tags_shown = "ALL";
  37$tags_hidden = [];
  38
  39_ANCHOR_TYPES = ["intersect","hull"];
  40
  41
  42// Section: Terminology and Shortcuts
  43//   This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
  44//   and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
  45//   - An anchor is a place on an object which you can align the object to, or attach other objects
  46//     to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
  47//     The direction and spin are used to orient other objects to match when using `attach()`.
  48//   - Spin is a simple rotation around the Z axis.
  49//   - Orientation is rotating an object so that its top is pointed towards a given vector.
  50//   .
  51//   An object will first be translated to its anchor position, then spun, then oriented.
  52//   For a detailed step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
  53//   .
  54//   For describing directions, faces, edges, and corners the library provides a set of shortcuts
  55//   all based on combinations of unit direction vectors.  You can use these for anchoring and orienting
  56//   attachable objects.  You can also them to specify edge sets for rounding or chamfering cuboids,
  57//   or for placing edge, face and corner masks.
  58// Subsection: Anchor
  59//   Anchoring is specified with the `anchor` argument in most shape modules.  Specifying `anchor`
  60//   when creating an object will translate the object so that the anchor point is at the origin
  61//   (0,0,0).  Anchoring always occurs before spin and orientation are applied.
  62//   .
  63//   An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
  64//   .
  65//   When given as a vector, it points, in a general way, towards the face, edge, or corner of the
  66//   object that you want the anchor for, relative to the center of the object.  You can simply
  67//   specify a vector like `[0,0,1]` to anchor an object at the Z+ end, but you can also use
  68//   directional constants with names like `TOP`, `BOTTOM`, `LEFT`, `RIGHT` and `BACK` that you can add together
  69//   to specify anchor points.  See [specifying directions](attachments.scad#subsection-specifying-directions)
  70//   below for the full list of pre-defined directional constants.
  71//   .
  72//   For example:
  73//   - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
  74//   - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
  75//   - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
  76//   .
  77//   When the object is cubical or rectangular in shape the anchors must have zero or one values
  78//   for their components and they refer to the face centers, edge centers, or corners of the object.
  79//   The direction of a face anchor will be perpendicular to the face, pointing outward.  The direction of a edge anchor
  80//   will be the average of the anchor directions of the two faces the edge is between.  The direction
  81//   of a corner anchor will be the average of the anchor directions of the three faces the corner is
  82//   on.
  83//   .
  84//   When the object is cylindrical, conical, or spherical in nature, the anchors will be located
  85//   around the surface of the cylinder, cone, or sphere, relative to the center.
  86//   You can generally use an arbitrary vector to get an anchor positioned anywhere on the curved
  87//   surface of such an object, and the anchor direction will be the surface normal at the anchor location.
  88//   However, for anchor component pointing toward the flat face should be either -1, 1, or 0, and
  89//   anchors that point diagonally toward one of the flat faces will select a point on the edge.
  90//   .
  91//   For objects in two dimensions, the natural expectation is for TOP and BOTTOM to refer to the Y direction
  92//   of the shape.  To support this, if you give an anchor in 2D that has anchor.y=0 then the Z component
  93//   will be mapped to the Y direction.  This  means you can use TOP and BOTTOM for anchors of 2D objects.
  94//   But remember that TOP and BOTTOM are three dimensional vectors and this is a special interpretation
  95//   for 2d anchoring.
  96//   .
  97//   Some more complex objects, like screws and stepper motors, have named anchors to refer to places
  98//   on the object that are not at one of the standard faces, edges or corners.  For example, stepper
  99//   motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
 100//   stepper motor shape.  The names, positions, directions, and spins of these anchors are
 101//   specific to the object, and are documented when they exist.
 102// Subsection: Spin
 103//   Spin is specified with the `spin` argument in most shape modules.  Specifying a scalar `spin`
 104//   when creating an object will rotate the object counter-clockwise around the Z axis by the given
 105//   number of degrees.  If given as a 3D vector, the object will be rotated around each of the X, Y, Z
 106//   axes by the number of degrees in each component of the vector.  Spin is always applied after
 107//   anchoring, and before orientation.  Since spin is applied after anchoring it is not what
 108//   you might think of intuitively as spinning the shape.  To do that, apply `zrot()` to the shape before anchoring.
 109// Subsection: Orient
 110//   Orientation is specified with the `orient` argument in most shape modules.  Specifying `orient`
 111//   when creating an object will rotate the object such that the top of the object will be pointed
 112//   at the vector direction given in the `orient` argument.  Orientation is always applied after
 113//   anchoring and spin.  The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
 114//   added together to form the directional vector for this.  ie: `LEFT+BACK`
 115// Subsection: Specifying Directions
 116//   You can use direction vectors to specify anchors for objects or to specify edges, faces, and
 117//   corners of cubes.  You can simply specify these direction vectors numerically, but another
 118//   option is to use named constants for direction vectors.  These constants define unit vectors
 119//   for the six axis directions as shown below.
 120// Figure(3D,Big,VPD=6): Named constants for direction vectors.  Some directions have more than one name.
 121//   $fn=12;
 122//   stroke([[0,0,0],RIGHT], endcap2="arrow2", width=.05);
 123//   color("black")right(.05)up(.05)move(RIGHT) text3d("RIGHT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 124//   stroke([[0,0,0],LEFT], endcap2="arrow2", width=.05);
 125//   color("black")left(.05)up(.05)move(LEFT) text3d("LEFT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 126//   stroke([[0,0,0],FRONT], endcap2="arrow2", width=.05);
 127//   color("black")
 128//   left(.1){
 129//   up(.12)move(FRONT) text3d("FRONT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 130//   move(FRONT) text3d("FWD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 131//   down(.12)move(FRONT) text3d("FORWARD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 132//   }
 133//   stroke([[0,0,0],BACK], endcap2="arrow2", width=.05);
 134//   right(.05)
 135//   color("black")move(BACK) text3d("BACK",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 136//   stroke([[0,0,0],DOWN], endcap2="arrow2", width=.05);
 137//   color("black")
 138//   right(.1){
 139//   up(.12)move(BOT) text3d("DOWN",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 140//   move(BOT) text3d("BOTTOM",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 141//   down(.12)move(BOT) text3d("BOT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 142//   }
 143//   stroke([[0,0,0],TOP], endcap2="arrow2", width=.05);
 144//   color("black")left(.05){
 145//   up(.12)move(TOP) text3d("TOP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 146//   move(TOP) text3d("UP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 147//   }
 148// Figure(2D,Big): Named constants for direction vectors in 2D.  For anchors the TOP and BOTTOM directions are collapsed into 2D as shown here, but do not try to use TOP or BOTTOM as 2D directions in other situations.
 149//   $fn=12;
 150//   stroke(path2d([[0,0,0],RIGHT]), endcap2="arrow2", width=.05);
 151//   color("black")fwd(.22)left(.05)move(RIGHT) text("RIGHT",size=.1,anchor=RIGHT);
 152//   stroke(path2d([[0,0,0],LEFT]), endcap2="arrow2", width=.05);
 153//   color("black")right(.05)fwd(.22)move(LEFT) text("LEFT",size=.1,anchor=LEFT);
 154//   stroke(path2d([[0,0,0],FRONT]), endcap2="arrow2", width=.05);
 155//   color("black")
 156//   fwd(.2)
 157//   right(.15)
 158//   color("black")move(BACK) { text("BACK",size=.1,anchor=LEFT); back(.14) text("(TOP)", size=.1, anchor=LEFT);}
 159//   color("black")
 160//   left(.15)back(.2+.14)move(FRONT){
 161//   back(.14) text("FRONT",size=.1,anchor=RIGHT);
 162//       text("FWD",size=.1,anchor=RIGHT);
 163//   fwd(.14) text("FORWARD",size=.1,anchor=RIGHT);
 164//   fwd(.28) text("(BOTTOM)",size=.1,anchor=RIGHT);
 165//   fwd(.14*3) text("(BOT)",size=.1,anchor=RIGHT);
 166//   }
 167//   stroke(path2d([[0,0,0],BACK]), endcap2="arrow2", width=.05);
 168// Subsection: Specifying Faces
 169//   Modules operating on faces accept a list of faces to describe the faces to operate on.  Each
 170//   face is given by a vector that points to that face.  Attachments of cuboid objects onto their faces also
 171//   work by choosing an attachment face with a single vector in the same manner.
 172// Figure(3D,Big,NoScales,VPD=275): The six faces of the cube.  Some have faces have more than one name.
 173//   ydistribute(50) {
 174//      xdistribute(35){
 175//        _show_cube_faces([BACK], botlabel=["BACK"]);
 176//        _show_cube_faces([UP],botlabel=["TOP","UP"]);
 177//        _show_cube_faces([RIGHT],botlabel=["RIGHT"]);
 178//      }
 179//      xdistribute(35){
 180//        _show_cube_faces([FRONT],toplabel=["FRONT","FWD", "FORWARD"]);
 181//        _show_cube_faces([DOWN],toplabel=["BOTTOM","BOT","DOWN"]);
 182//        _show_cube_faces([LEFT],toplabel=["LEFT"]);
 183//      }
 184//   }
 185// Subsection: Specifying Edges
 186//   Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
 187//   is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
 188//   edge set descriptors to remove from the edge set.
 189//   The default value for `edges` is `"ALL"`, the set of all edges.
 190//   The default value for `except` is the    empty set, meaning no edges are removed.
 191//   If either argument is just a single edge set
 192//   descriptor it can be passed directly rather than in a singleton list.
 193//   Each edge set descriptor must be one of:
 194//   - A vector pointing towards an edge, indicating that single edge.
 195//   - A vector pointing towards a face, indicating all edges surrounding that face.
 196//   - A vector pointing towards a corner, indicating all edges touching that corner.
 197//   - The string `"X"`, indicating all X axis aligned edges.
 198//   - The string `"Y"`, indicating all Y axis aligned edges.
 199//   - The string `"Z"`, indicating all Z axis aligned edges.
 200//   - The string `"ALL"`, indicating all edges.
 201//   - The string `"NONE"`, indicating no edges at all.
 202//   - A 3x4 array, where each entry corresponds to one of the 12 edges and is set to 1 if that edge is included and 0 if the edge is not.  The edge ordering is:
 203//       ```
 204//       [
 205//           [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
 206//           [X-Z-, X+Z-, X-Z+, X+Z+],
 207//           [X-Y-, X+Y-, X-Y+, X+Y+]
 208//       ]
 209//       ```
 210//   .
 211//   You can specify edge descriptors directly by giving a vector, or you can use sums of the
 212//   named direction vectors described above.  Below we show all of the edge sets you can
 213//   describe with sums of the direction vectors, and then we show some examples of combining
 214//   edge set descriptors.
 215// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
 216//   ydistribute(50) {
 217//       xdistribute(30) {
 218//           _show_edges(edges=BOT+RIGHT);
 219//           _show_edges(edges=BOT+BACK);
 220//           _show_edges(edges=BOT+LEFT);
 221//           _show_edges(edges=BOT+FRONT);
 222//       }
 223//       xdistribute(30) {
 224//           _show_edges(edges=FWD+RIGHT);
 225//           _show_edges(edges=BACK+RIGHT);
 226//           _show_edges(edges=BACK+LEFT);
 227//           _show_edges(edges=FWD+LEFT);
 228//       }
 229//       xdistribute(30) {
 230//           _show_edges(edges=TOP+RIGHT);
 231//           _show_edges(edges=TOP+BACK);
 232//           _show_edges(edges=TOP+LEFT);
 233//           _show_edges(edges=TOP+FRONT);
 234//       }
 235//   }
 236// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
 237//   ydistribute(50) {
 238//       xdistribute(30) {
 239//           _show_edges(edges=LEFT);
 240//           _show_edges(edges=FRONT);
 241//           _show_edges(edges=RIGHT);
 242//       }
 243//       xdistribute(30) {
 244//           _show_edges(edges=TOP);
 245//           _show_edges(edges=BACK);
 246//           _show_edges(edges=BOTTOM);
 247//       }
 248//   }
 249// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
 250//   ydistribute(50) {
 251//       xdistribute(30) {
 252//           _show_edges(edges=FRONT+LEFT+TOP);
 253//           _show_edges(edges=FRONT+RIGHT+TOP);
 254//           _show_edges(edges=FRONT+LEFT+BOT);
 255//           _show_edges(edges=FRONT+RIGHT+BOT);
 256//       }
 257//       xdistribute(30) {
 258//           _show_edges(edges=TOP+LEFT+BACK);
 259//           _show_edges(edges=TOP+RIGHT+BACK);
 260//           _show_edges(edges=BOT+LEFT+BACK);
 261//           _show_edges(edges=BOT+RIGHT+BACK);
 262//       }
 263//   }
 264// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
 265//   ydistribute(50) {
 266//       xdistribute(30) {
 267//           _show_edges(edges="X");
 268//           _show_edges(edges="Y");
 269//           _show_edges(edges="Z");
 270//       }
 271//       xdistribute(30) {
 272//           _show_edges(edges="ALL");
 273//           _show_edges(edges="NONE");
 274//       }
 275//   }
 276// Figure(3D,Big,VPD=310,NoScales):  Next are some examples showing how you can combine edge descriptors to obtain different edge sets.    You can specify the top front edge with a numerical vector or by combining the named direction vectors.  If you combine them as a list you get all the edges around the front and top faces.  Adding `except` removes an edge.
 277//   xdistribute(43){
 278//     _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]);
 279//     _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]);
 280//     _show_edges(_edges([TOP,FRONT]),toplabel=["edges=[TOP,FRONT]"]);
 281//     _show_edges(_edges([TOP,FRONT],TOP+FRONT),toplabel=["edges=[TOP,FRONT]","except=TOP+FRONT"]);
 282//   }
 283// Figure(3D,Big,VPD=310,NoScales): Using `except=BACK` removes the four edges surrounding the back face if they are present in the edge set.  In the first example only one edge needs to be removed.  In the second example we remove two of the Z-aligned edges.  The third example removes all four back edges from the default edge set of all edges.  You can explicitly give `edges="ALL"` but it is not necessary, since this is the default.  In the fourth example, the edge set of Y-aligned edges contains no back edges, so the `except` parameter has no effect.
 284//   xdistribute(43){
 285//     _show_edges(_edges(BOT,BACK), toplabel=["edges=BOT","except=BACK"]);
 286//     _show_edges(_edges("Z",BACK), toplabel=["edges=\"Z\"", "except=BACK"]);
 287//     _show_edges(_edges("ALL",BACK), toplabel=["(edges=\"ALL\")", "except=BACK"]);
 288//     _show_edges(_edges("Y",BACK), toplabel=["edges=\"Y\"","except=BACK"]);
 289//   }
 290// Figure(3D,Big,NoScales,VPD=310): On the left `except` is a list to remove two edges.  In the center we show a corner edge set defined by a numerical vector, and at the right we remove that same corner edge set with named direction vectors.
 291//   xdistribute(52){
 292//    _show_edges(_edges("ALL",[FRONT+RIGHT,FRONT+LEFT]),
 293//               toplabel=["except=[FRONT+RIGHT,","       FRONT+LEFT]"]);
 294//    _show_edges(_edges([1,-1,1]),toplabel=["edges=[1,-1,1]"]);
 295//    _show_edges(_edges([TOP,BOT], TOP+RIGHT+FRONT),toplabel=["edges=[TOP,BOT]","except=TOP+RIGHT+FRONT"]);
 296//   }
 297// Subsection: Specifying Corners
 298//   Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument
 299//   is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of
 300//   corner set descriptors to remove from the corner set.
 301//   The default value for `corners` is `"ALL"`, the set of all corners.
 302//   The default value for `except` is the   empty set, meaning no corners are removed.
 303//   If either argument is just a single corner set
 304//   descriptor it can be passed directly rather than in a singleton list.
 305//   Each corner set descriptor must be one of:
 306//   - A vector pointing towards a corner, indicating that corner.
 307//   - A vector pointing towards an edge indicating both corners at the ends of that edge.
 308//   - A vector pointing towards a face, indicating all the corners of that face.
 309//   - The string `"ALL"`, indicating all corners.
 310//   - The string `"NONE"`, indicating no corners at all.
 311//   - A length 8 vector where each entry corresponds to a corner and is 1 if the corner is included and 0 if it is excluded.  The corner ordering is
 312//       ```
 313//       [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
 314//       ```
 315//   .
 316//   You can specify corner descriptors directly by giving a vector, or you can use sums of the
 317//   named direction vectors described above.  Below we show all of the corner sets you can
 318//   describe with sums of the direction vectors and then we show some examples of combining
 319//   corner set descriptors.
 320// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
 321//   ydistribute(55) {
 322//       xdistribute(35) {
 323//           _show_corners(corners=FRONT+LEFT+TOP);
 324//           _show_corners(corners=FRONT+RIGHT+TOP);
 325//           _show_corners(corners=FRONT+LEFT+BOT);
 326//           _show_corners(corners=FRONT+RIGHT+BOT);
 327//       }
 328//       xdistribute(35) {
 329//           _show_corners(corners=TOP+LEFT+BACK);
 330//           _show_corners(corners=TOP+RIGHT+BACK);
 331//           _show_corners(corners=BOT+LEFT+BACK);
 332//           _show_corners(corners=BOT+RIGHT+BACK);
 333//       }
 334//   }
 335// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
 336//   ydistribute(55) {
 337//       xdistribute(35) {
 338//           _show_corners(corners=BOT+RIGHT);
 339//           _show_corners(corners=BOT+BACK);
 340//           _show_corners(corners=BOT+LEFT);
 341//           _show_corners(corners=BOT+FRONT);
 342//       }
 343//       xdistribute(35) {
 344//           _show_corners(corners=FWD+RIGHT);
 345//           _show_corners(corners=BACK+RIGHT);
 346//           _show_corners(corners=BACK+LEFT);
 347//           _show_corners(corners=FWD+LEFT);
 348//       }
 349//       xdistribute(35) {
 350//           _show_corners(corners=TOP+RIGHT);
 351//           _show_corners(corners=TOP+BACK);
 352//           _show_corners(corners=TOP+LEFT);
 353//           _show_corners(corners=TOP+FRONT);
 354//       }
 355//   }
 356// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
 357//   ydistribute(55) {
 358//       xdistribute(35) {
 359//           _show_corners(corners=LEFT);
 360//           _show_corners(corners=FRONT);
 361//           _show_corners(corners=RIGHT);
 362//       }
 363//       xdistribute(35) {
 364//           _show_corners(corners=TOP);
 365//           _show_corners(corners=BACK);
 366//           _show_corners(corners=BOTTOM);
 367//       }
 368//   }
 369// Figure(3D,Med,NoScales,VPD=200): Corners by name
 370//   xdistribute(35) {
 371//       _show_corners(corners="ALL");
 372//       _show_corners(corners="NONE");
 373//   }
 374// Figure(3D,Big,NoScales,VPD=300):     Next are some examples showing how you can combine corner descriptors to obtain different corner sets.   You can specify corner sets numerically or by adding together named directions.  The third example shows a list of two corner specifications, giving all the corners on the front face or the right face.
 375//   xdistribute(52){
 376//     _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]);
 377//     _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]);
 378//     _show_corners(_corners([FRONT,RIGHT]), toplabel=["corners=[FRONT,RIGHT]"]);
 379//   }
 380// Figure(3D,Big,NoScales,VPD=300): Corners for one edge, two edges, and all the edges except the two on one edge.  Note that since the default is all edges, you only need to give the except argument in this case:
 381//    xdistribute(52){
 382//      _show_corners(_corners(FRONT+TOP), toplabel=["corners=FRONT+TOP"]);
 383//       _show_corners(_corners([FRONT+TOP,BOT+BACK]), toplabel=["corners=[FRONT+TOP,","        BOT+BACK]"]);
 384//       _show_corners(_corners("ALL",FRONT+TOP), toplabel=["(corners=\"ALL\")","except=FRONT+TOP"]);
 385//    }
 386// Figure(3D,Med,NoScales,VPD=240): The first example shows a single corner removed from the top corners using a numerical vector.  The second one shows removing a set of two corner descriptors from the implied set of all corners.
 387//    xdistribute(58){
 388//       _show_corners(_corners(TOP,[1,1,1]), toplabel=["corners=TOP","except=[1,1,1]"]);
 389//       _show_corners(_corners("ALL",[FRONT+RIGHT+TOP,FRONT+LEFT+BOT]),
 390//                    toplabel=["except=[FRONT+RIGHT+TOP,","       FRONT+LEFT+BOT]"]);
 391//    }
 392// Subsection: Anchoring of Non-Rectangular Objects and Anchor Type (atype)
 393//   We focused above on rectangular objects that have well-defined faces and edges aligned with the coordinate axes.
 394//   Things get difficult when the objects are curved, or even when their edges are not neatly aligned with the coordinate axes.
 395//   In these cases, the library may provide multiple different anchoring schemes, called the anchor types.  When a module supports
 396//   multiple anchor types, use the `atype=` parameter to select the anchor type you need.
 397// .
 398//   First consider the case of a simple rectangle whose corners have been rounded.  Where should the anchors lie?
 399//   The default anchor type puts them in the same location as the anchors of an unrounded rectangle, which means that for
 400//   positive rounding radii, they are not even located on the perimeter of the object.
 401// Figure(2D,Med,NoAxes): Default "box" atype anchors for a rounded {{rect()}}
 402//   rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0]) show_anchors();
 403// Continues:
 404//   This choice enables you to position the box, or attach things to it, without regard to its rounding or chamfers.  If you need to
 405//   anchor onto the roundovers or chamfers then you can use the "perim" anchor type:
 406// Figure(2D,Med,NoAxes): The "perim" atype for a rounded and chamfered {{rect()}}
 407//   rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0],atype="perim") show_anchors();
 408// Continues:
 409//   With this anchor type, the anchors are located on the perimeter.  For positive roundings they point in the standard anchor direction;
 410//   for negative roundings they are parallel to the base.  As noted above, for circles, cylinders, and spheres, the anchor point is
 411//   determined by choosing the point where the anchor vector intersects the shape.  On a circle, this results in an anchor whose direction
 412//   matches the user provided anchor vector.  But on an ellipse, something else happens:
 413// Figure(2D,Med,NoAxes): Anchors on an ellipse.  The red arrow shows a TOP+RIGHT anchor direction. 
 414//   ellipse([70,30]) show_anchors();
 415//   stroke([[0,0],[45,45]], color="red",endcap2="arrow2");
 416// Continues:
 417//   For a TOP+RIGHT anchor direction, the surface normal at the intersection point does not match the anchor direction,
 418//   so the direction of the anchor shown in blue does not match the direction specified, in red.
 419//   Anchors computed this way have anchor type "intersect".  When a shape is concave, intersection anchors can produce
 420//   a result buried inside the shape's concavity.  Consider the RIGHT anchor of this supershape example:
 421// Figure(2D,Med,NoAxes): A supershape with "intersect" anchor type:
 422//   supershape(n=150,r=75, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="intersect") show_anchors();
 423// Continues:
 424//   A different anchor type called "hull" finds anchors that are on the convex hull of the shape.  
 425// Figure(2D,Med,NoAxes): A supershape with "hull" anchor type:
 426//   supershape(n=150,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") show_anchors();
 427// Continues:
 428//   Hull anchoring works by creating the line (or plane in 3D) that is normal to the specified anchor direction, and
 429//   finding the point farthest from the center that intersects that line (or plane).
 430// Figure(2D,Med,NoAxes): Finding the RIGHT and BACK+LEFT "hull" anchors
 431//   supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") {
 432//     position(RIGHT) color_this("red")rect([1,90],anchor=LEFT);
 433//     attach(RIGHT)anchor_arrow2d(13);
 434//     attach(BACK+LEFT) {
 435//        anchor_arrow2d(13);
 436//        color_this("red")rect([30,1]);
 437//        }
 438//     }
 439// Continues:
 440//   In the example the RIGHT anchor is found when the normal line (shown in red) is tangent to the shape at two points.
 441//   The anchor is then taken to be the midpoint.  The BACK+LEFT anchor occurs with a single tangent point, and the
 442//   anchor point is located at the tangent point.  For circles intersection is done to the exact circle, but for other
 443//   shapes these calculations are done on the point lists that defines the shape, so if you change the number of points
 444//   in the list, the precise location of the anchors can change.  You can also get surprising results if your point list is badly chosen.
 445// Figure(2D,Med,NoAxes): Circle anchor in blue.  The red anchor is computed to a point list of a circle with 17 segments.  
 446//   circle(r=31,$fn=128) attach(TOP)anchor_arrow2d(15);
 447//   region(circle(r=33,$fn=17)) {color("red")attach(TOP)anchor_arrow2d(13);}
 448// Continues:
 449//   The figure shows a large horizontal offset due to a poor choice of sampling for the circular shape when using the "hull" anchor type.
 450//   The determination of "hull" or "intersect" anchors may depend on the location of the centerpoint used in the computation.
 451//   Some of the modules allow you to change the centerpoint using a `cp=` argument.  If you need to change the centerpoint for
 452//   a module that does not provide this option, you can use the generic {{region()}} module, which will let you specify a centerpoint.
 453//   The default center point is the centroid, specified by "centroid".  You can also choose "mean", which gives the mean of all
 454//   the data points, or "bbox", which gives the centerpoint of the bounding box for the data.  Your last option for centerpoint is to
 455//   choose an arbitrary point that meets your needs.
 456// Figure(2D,Med,NoAxes): The centerpoint for "intersect" anchors is located at the red dot
 457//   region(supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9),atype="intersect",cp=[0,30]) show_anchors();
 458//   color("red")back(30)circle(r=2,$fn=16);
 459// Continues:
 460//   Note that all the anchors for an object have to be determined based on one anchor type and relative to the same centerpoint.
 461//   The supported anchor types for each module appear in the "Anchor Types" section of its entry.  
 462
 463
 464
 465
 466
 467// Section: Attachment Positioning
 468
 469// Module: position()
 470// Synopsis: Attaches children to a parent object at an anchor point.
 471// SynTags: Trans
 472// Topics: Attachments
 473// See Also: attachable(), attach(), orient()
 474// Usage:
 475//   PARENT() position(at) CHILDREN;
 476// Description:
 477//   Attaches children to a parent object at an anchor point.  For a step-by-step explanation
 478//   of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 479// Arguments:
 480//   at = The vector, or name of the parent anchor point to attach to.
 481// Side Effects:
 482//   `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 483//   `$attach_to` is set to `undef`.
 484//   `$attach_norot` is set to `true`.
 485// Example:
 486//   spheroid(d=20) {
 487//       position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 488//       position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 489//       position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 490//   }
 491module position(at,from)
 492{
 493    if (is_def(from)){
 494      echo("'from' argument of position() has changed to 'at' and will be removed in a future version");
 495    }
 496    dummy0=assert(num_defined([at,from])==1, "Cannot give both `at` argument and the deprectated `from` argument to position()");
 497    at = first_defined([at,from]);
 498    req_children($children);
 499    dummy1=assert($parent_geom != undef, "No object to position relative to.");
 500    anchors = (is_vector(at)||is_string(at))? [at] : at;
 501    two_d = _attach_geom_2d($parent_geom);
 502    for (anchr = anchors) {
 503        anch = _find_anchor(anchr, $parent_geom);
 504        $attach_to = undef;
 505        $attach_anchor = anch;
 506        $attach_norot = true;
 507        translate(anch[1]) children();
 508    }
 509}
 510
 511
 512
 513// Module: orient()
 514// Synopsis: Orients children's tops in the directon of the specified anchor.
 515// SynTags: Trans
 516// Topics: Attachments
 517// See Also: attachable(), attach(), position()
 518// Usage:
 519//   PARENT() orient(anchor, [spin]) CHILDREN;
 520// Description:
 521//   Orients children such that their top is tilted in the direction of the specified parent anchor point. 
 522//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 523// Arguments:
 524//   anchor = The anchor on the parent which you want to match the orientation of.
 525//   spin = The spin to add to the children.  (Overrides anchor spin.)
 526// Side Effects:
 527//   `$attach_to` is set to `undef`.
 528//   `$attach_norot` is set to `true`.
 529// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
 530//   prismoid([50,50],[30,30],h=40) {
 531//       position(TOP+RIGHT)
 532//           orient(RIGHT)
 533//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 534//   }
 535// Example: You can override anchor spin with `spin=`.
 536//   prismoid([50,50],[30,30],h=40) {
 537//       position(TOP+RIGHT)
 538//           orient(RIGHT,spin=0)
 539//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 540//   }
 541// Example: Or you can anchor the child from the back
 542//   prismoid([50,50],[30,30],h=40) {
 543//       position(TOP+RIGHT)
 544//           orient(RIGHT)
 545//               prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
 546//   }
 547module orient(anchor, spin) {
 548    req_children($children);
 549    check=
 550      assert($parent_geom != undef, "No parent to orient from!")
 551      assert(is_string(anchor) || is_vector(anchor));
 552    anch = _find_anchor(anchor, $parent_geom);
 553    two_d = _attach_geom_2d($parent_geom);
 554    fromvec = two_d? BACK : UP;
 555    spin = default(spin, anch[3]);
 556    dummy=assert(is_finite(spin));
 557
 558    $attach_to = undef;
 559    $attach_norot = true;
 560    if (two_d)
 561        rot(spin)rot(from=fromvec, to=anch[2]) children();
 562    else
 563        rot(spin, from=fromvec, to=anch[2]) children();
 564}
 565
 566
 567
 568// Module: align()
 569// Synopsis: Position and orient children with alignment to parent edges.
 570// SynTags: Trans
 571// Topics: Attachments
 572// See Also: attachable(), attach(), position(), orient()
 573// Usage:
 574//   PARENT() align(anchor, [orient], [spin], [inside=]) CHILDREN;
 575// Description:
 576//   Positions children to the specified anchor(s) on the parent and anchors the
 577//   children so that they are aligned with the edge(s) of the parent at those parent anchors.
 578//   You can specify a parent anchor point in `orient` and in this case, the top of the child
 579//   is tilted in the direction of that anchor.
 580//   This means you can easily place children so they are aligned flush with edges of the parent.
 581//   In contrast, with {{position()}} you will have to work out the correct anchor for the children
 582//   which is not always obvious.  It also enables you to place several children that have different
 583//   anchors, which would otherwise require several {{position()}} calls.  The inside argument
 584//   causes the object to appear inside the parent for use with {{diff()}}.  
 585//   .
 586//   When you use `align()`, the `orient=` and `anchor=` arguments to the child objects are overriden,
 587//   so they do not have any effect.  The `spin=` argument to the child still applies. 
 588// Arguments:
 589//   anchor = parent anchor or list of parent anchors for positioning children
 590//   orient = parent anchor to give direction for orienting the children.  Default: UP
 591//   spin = spin in degrees for rotating the children.  Default: Derived from orient anchor
 592//   ---
 593//   inside = if true, place object inside the parent instead of outside.  Default: false
 594// Side Effects:
 595//   `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 596//   `$attach_to` is set to `undef`.
 597//   `$attach_norot` is set to `true`.
 598//   `$anchor_override` is set to the anchor required for proper positioning of the child.  
 599//   if inside is true then set default tag to "remove"
 600// Example: Child would require anchor of RIGHT+FRONT+BOT if placed with {{position()}}. 
 601//   cuboid([50,40,15])
 602//     align(RIGHT+FRONT+TOP)
 603//       color("lightblue")prismoid([10,5],[7,4],height=4);
 604// Example: Child requires a different anchor for each position, so explicit specification of the anchor for children is impossible in this case, without using two separate commands.
 605//   cuboid([50,40,15])
 606//     align([RIGHT+TOP,LEFT+TOP])
 607//       color("lightblue")prismoid([10,5],[7,4],height=4);
 608// Example: If you try to spin your child, the spin happens after the alignment anchor, so the child will not be flush:
 609//   cuboid([50,40,15])
 610//     align([RIGHT+TOP])
 611//       color("lightblue")
 612//          prismoid([10,5],[7,4],height=4,spin=90);
 613// Example: You can instead spin the attached children using the spin parameter to `align()`.  In this example, the required anchor is BOT+FWD, which is less obvious.
 614//   cuboid([50,40,15])
 615//     align(RIGHT+TOP,spin=90)
 616//       color("lightblue")prismoid([10,5],[7,4],height=4);
 617// Example: Here the child is oriented to the RIGHT, so it appears flush with the top.  In this case you don't have to figure out that the required child anchor is BOT+BACK.  
 618//   cuboid([50,40,15])
 619//     align(RIGHT+TOP,RIGHT)
 620//       color("lightblue")prismoid([10,5],[7,4],height=4);
 621// Example: If you change the orientation the child still appears aligned flush in its changed orientation:
 622//   cuboid([50,40,15])
 623//     align(RIGHT+TOP,DOWN)
 624//       color("lightblue")prismoid([10,5],[7,4],height=4);
 625// Example: Objects on the right already have nonzero spin by default, so setting spin=0 changes the spin:
 626//   prismoid(50,30,25){
 627//     align(RIGHT+TOP,RIGHT,spin=0)
 628//       color("lightblue")prismoid([10,5],[7,4],height=4);
 629//     align(RIGHT+BOT,RIGHT)
 630//       color("green")prismoid([10,5],[7,4],height=4);
 631//   }
 632// Example: Setting inside=true enables us to subtract the child from the parent with {{diff()}.  The "remove" tag is automatically applied when you set `inside=true`.  
 633//   diff()
 634//     cuboid([40,30,10])
 635//       move(.1*[0,-1,1])
 636//         align(FRONT+TOP,inside=true)
 637//           prismoid([10,5],[7,5],height=4);
 638module align(anchor,orient=UP,spin,inside=false)
 639{
 640    req_children($children);
 641    dummy1=assert($parent_geom != undef, "No object to align to.")
 642           assert(is_string(orient) || is_vector(orient),"Bad orient value");
 643    position_anchors = (is_vector(anchor)||is_string(anchor))? [anchor] : anchor;
 644    two_d = _attach_geom_2d($parent_geom);
 645    fromvec = two_d? BACK : UP;
 646
 647    orient_anch = _find_anchor(orient, $parent_geom);
 648    spin = default(spin, orient_anch[3]);
 649    dummy2=assert(is_finite(spin));
 650    
 651    $attach_to = undef;
 652    $attach_norot = true;
 653
 654    factor = inside?1:-1;
 655    
 656    for (thisanch = position_anchors) {
 657        pos_anch = _find_anchor(thisanch, $parent_geom);
 658        init_anch = two_d ? rot(from=orient_anch[2], to=fromvec, p=zrot(-spin,pos_anch[0]))
 659                          : rot(spin, from=fromvec, to=orient_anch[2], reverse=true, p=pos_anch[0]);
 660        quant_anch = [for(v=init_anch) sign(round(v))];
 661        $anchor_override = two_d && quant_anch.y!=0 ? [quant_anch.x,factor*quant_anch.y]
 662                         : !two_d && quant_anch.z!=0 ? [quant_anch.x,quant_anch.y, factor*quant_anch.z]
 663                         : factor*quant_anch;
 664        $attach_anchor = pos_anch;
 665        translate(pos_anch[1]) {
 666            if (two_d)
 667                rot(spin)rot(from=fromvec, to=orient_anch[2])
 668                  default_tag("remove",inside) children();
 669            else
 670                rot(spin, from=fromvec, to=orient_anch[2])
 671                  default_tag("remove",inside) children();                  
 672        }
 673    }
 674}
 675
 676
 677
 678
 679
 680// Module: attach()
 681// Synopsis: Attaches children to a parent object at an anchor point and orientation.
 682// SynTags: Trans
 683// Topics: Attachments
 684// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
 685// Usage:
 686//   PARENT() attach(from, [overlap=], [norot=]) CHILDREN;
 687//   PARENT() attach(from, to, [overlap=], [norot=]) CHILDREN;
 688// Description:
 689//   Attaches children to a parent object at an anchor point and orientation.  Attached objects will
 690//   be overlapped into the parent object by a little bit, as specified by the `$overlap`
 691//   value (0 by default), or by the overriding `overlap=` argument.  This is to prevent OpenSCAD
 692//   from making non-manifold objects.  You can define `$overlap=` as an argument in a parent
 693//   module to set the default for all attachments to it.  For a step-by-step explanation of
 694//   attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 695// Arguments:
 696//   from = The vector, or name of the parent anchor point to attach to.
 697//   to = Optional name of the child anchor point.  If given, orients the child such that the named anchors align together rotationally.
 698//   ---
 699//   overlap = Amount to sink child into the parent.  Equivalent to `down(X)` after the attach.  This defaults to the value in `$overlap`, which is `0` by default.
 700//   norot = If true, don't rotate children when attaching to the anchor point.  Only translate to the anchor point.
 701// Side Effects:
 702//   `$idx` is set to the index number of each anchor if a list of anchors is given.  Otherwise is set to `0`.
 703//   `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 704//   `$attach_to` is set to the value of the `to=` argument, if given.  Otherwise, `undef`
 705//   `$attach_norot` is set to the value of the `norot=` argument.
 706// Example:
 707//   spheroid(d=20) {
 708//       attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
 709//       attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
 710//       attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
 711//   }
 712module attach(from, to, overlap, norot=false)
 713{
 714    req_children($children);
 715    assert($parent_geom != undef, "No object to attach to!");
 716    overlap = (overlap!=undef)? overlap : $overlap;
 717    anchors = (is_vector(from)||is_string(from))? [from] : from;
 718    for ($idx = idx(anchors)) {
 719        anchr = anchors[$idx];
 720        anch = _find_anchor(anchr, $parent_geom);
 721        two_d = _attach_geom_2d($parent_geom);
 722        $attach_to = to;
 723        $attach_anchor = anch;
 724        $attach_norot = norot;
 725        olap = two_d? [0,-overlap,0] : [0,0,-overlap];
 726        if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
 727            translate(anch[1]) translate(olap) children();
 728        } else {
 729            fromvec = two_d? BACK : UP;
 730            translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
 731        }
 732    }
 733}
 734
 735// Section: Tagging
 736
 737// Module: tag()
 738// Synopsis: Assigns a tag to an object
 739// Topics: Attachments
 740// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 741// Usage:
 742//   PARENT() tag(tag) CHILDREN;
 743// Description:
 744//   Assigns the specified tag to all of the children. Note that if you want
 745//   to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
 746//   This works by setting the `$tag` variable, but it provides extra error checking and
 747//   handling of scopes.  You may set `$tag` directly yourself, but this is not recommended.
 748//   .
 749//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 750// Arguments:
 751//   tag = tag string, which must not contain any spaces.
 752// Side Effects:
 753//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 754// Example(3D):  Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one.
 755//   diff("remove")
 756//     cuboid(10){
 757//       position(TOP) cuboid(3);
 758//       tag("remove")
 759//       {
 760//         position(FRONT) cuboid(3);
 761//         position(RIGHT) cuboid(3);
 762//       }
 763//     }
 764module tag(tag)
 765{
 766    req_children($children);
 767    check=
 768      assert(is_string(tag),"tag must be a string")
 769      assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
 770    $tag = str($tag_prefix,tag);
 771    children();
 772}
 773
 774
 775// Module: force_tag()
 776// Synopsis: Assigns a tag to a non-attachable object.
 777// Topics: Attachments
 778// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
 779// Usage:
 780//   PARENT() force_tag([tag]) CHILDREN;
 781// Description:
 782//   You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
 783//   It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
 784//   making a final determination about whether to show or hide the children.
 785//   This means that tagging in children's children will be ignored.
 786//   This module is specifically provided for operating on children that are not tag aware such as modules
 787//   that don't use {{attachable()}} or built in modules such as
 788//   - `polygon()`
 789//   - `projection()`
 790//   - `polyhedron()`  (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
 791//   - `linear_extrude()`  (or use [`linear_sweep()`](regions.scad#linear_sweep))
 792//   - `rotate_extrude()`
 793//   - `surface()`
 794//   - `import()`
 795//   - `difference()`
 796//   - `intersection()`
 797//   - `hull()`
 798//   .
 799//   When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
 800//   Any time a test occurs for display of child() that test will succeed.  This means that when diff() checks
 801//   to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
 802//   it will subtract it.  The result will be a hole, possibly with zero-thickness edges or faces.  In order to
 803//   get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
 804//   that are not tagged.
 805//   .
 806//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 807// Arguments:
 808//   tag = tag string, which must not contain any spaces
 809// Side Effects:
 810//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 811// Example(2D): This example produces the full square without subtracting the "remove" item.  When you use non-attachable modules with tags, results are unpredictable.
 812//   diff()
 813//   {
 814//     polygon(square(10));
 815//     move(-[.01,.01])polygon(square(5),$tag="remove");
 816//   }
 817// Example(2D): Adding force_tag() fixes the model.  Note you need to add it to *every* non-attachable module, even the untagged ones, as shown here.
 818//   diff()
 819//   {
 820//     force_tag()
 821//       polygon(square(10));
 822//     force_tag("remove")
 823//       move(-[.01,.01])polygon(square(5));
 824//   }
 825module force_tag(tag)
 826{
 827    req_children($children);
 828    check1=assert(is_undef(tag) || is_string(tag),"tag must be a string");
 829    $tag = str($tag_prefix,default(tag,$tag));
 830    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"));
 831    if(_is_shown())
 832      show_all()
 833        children();
 834}
 835
 836
 837
 838// Module: default_tag()
 839// Synopsis: Sets a default tag for all children.
 840// Topics: Attachments
 841// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 842// Usage:
 843//   PARENT() default_tag(tag) CHILDREN;
 844// Description:
 845//   Sets a default tag for all of the children.  This is intended to be used to set a tag for a whole module
 846//   that is then used outside the module, such as setting the tag to "remove" for easy operation with {{diff()}}.
 847//   The default_tag() module sets the `$tag` variable only if it is not already
 848//   set so you can have a module set a default tag of "remove" but that tag can be overridden by a {{tag()}}
 849//   in force from a parent.  If you use {{tag()}} it will override any previously
 850//   specified tag from a parent, which can be very confusing to a user trying to change the tag on a module.
 851//   The `do_tag` parameter allows you to apply a default tag conditionally without having to repeat the children.  
 852//   .
 853//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 854// Arguments:
 855//   tag = tag string, which must not contain any spaces.
 856//   do_tag = if false do not set the tag.  
 857// Side Effects:
 858//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 859// Example(3D):  The module thing() is defined with {{tag()}} and the user applied tag of "keep_it" is ignored, leaving the user puzzled.
 860//   module thing() { tag("remove") cuboid(10);}
 861//   diff()
 862//     cuboid(20){
 863//       position(TOP) thing();
 864//       position(RIGHT) tag("keep_it") thing();
 865//   }
 866// Example(3D):  Using default_tag() fixes this problem: the user applied tag does not get overridden by the tag hidden in the module definition.
 867//   module thing() { default_tag("remove") cuboid(10);}
 868//   diff()
 869//     cuboid(20){
 870//       position(TOP) thing();
 871//       position(RIGHT) tag("keep_it") thing();
 872//   }
 873module default_tag(tag,do_tag=true)
 874{
 875    if ($tag=="" && do_tag) tag(tag) children();
 876    else children();
 877}
 878
 879
 880// Module: tag_scope()
 881// Synopsis: Creates a new tag scope.
 882// See Also: tag(), force_tag(), default_tag()
 883// Topics: Attachments
 884// Usage:
 885//   tag_scope([scope]) CHILDREN;
 886// Description:
 887//   Creates a tag scope with locally altered tag names to avoid tag name conflict with other code.
 888//   This is necessary when writing modules because the module's caller might happen to use the same tags.
 889//   Note that if you directly set the `$tag` variable then tag scoping will not work correctly.
 890// Side Effects:
 891//   `$tag_prefix` is set to the value of `scope=` if given, otherwise is set to a random string.
 892// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent.
 893//   module ring(r,h,w=1,anchor,spin,orient)
 894//   {
 895//     tag_scope("ringscope")
 896//       attachable(anchor,spin,orient,r=r,h=h){
 897//         diff()
 898//           cyl(r=r,h=h)
 899//             tag("remove") cyl(r=r-w,h=h+1);
 900//         children();
 901//       }
 902//   }
 903//   // Calling the module using "remove" tags
 904//   // will conflict with internal tag use in
 905//   // the ring module.
 906//   $fn=32;
 907//   diff(){
 908//       ring(10,7,w=4);
 909//       tag("remove")ring(8,8);
 910//       tag("remove")diff("rem"){
 911//          ring(9.5,8,w=1);
 912//          tag("rem")ring(9.5,8,w=.3);
 913//       }
 914//     }
 915module tag_scope(scope){
 916  req_children($children);
 917  scope = is_undef(scope) ? rand_str(20) : scope;
 918  assert(is_string(scope), "scope must be a string");
 919  assert(undef==str_find(scope," "),str("Scope string \"",scope,"\" contains a space, which is not allowed"));
 920  $tag_prefix=scope;
 921  children();
 922}
 923
 924
 925// Section: Attachment Modifiers
 926
 927// Module: diff()
 928// Synopsis: Performs a differencing operation using tags rather than hierarchy to control what happens.
 929// Topics: Attachments
 930// See Also: tag(), force_tag(), recolor(), show_only(), hide(), tag_diff(), intersect(), tag_intersect()
 931// Usage:
 932//   diff([remove], [keep]) PARENT() CHILDREN;
 933// Description:
 934//   Performs a differencing operation using tags to control what happens.  This is specifically intended to
 935//   address the situation where you want differences between a parent and child object, something
 936//   that is impossible with the native difference() module.
 937//   The children to diff are grouped into three categories, regardless of nesting level.
 938//   The `remove` argument is a space delimited list of tags specifying objects to
 939//   subtract.  The `keep` argument is a similar list of tags giving objects to be kept.
 940//   Objects not matching either the `remove` or `keep` lists form the third category of base objects.
 941//   To produce its output, diff() forms the union of all the base objects and then
 942//   subtracts all the objects with tags in `remove`.  Finally it adds in objects listed in `keep`.
 943//   Attachable objects should be tagged using {{tag()}}
 944//   and non-attachable objects with {{force_tag()}}.
 945//   .
 946//   Remember when using tagged operations with that the operations don't happen in hierarchical order, since
 947//   the point of tags is to break the hierarchy.  If you tag an object with a keep tag, nothing will be
 948//   subtracted from it, no matter where it appears because kept objects are unioned in at the end.
 949//   If you want a child of an object tagged with a remove tag to stay in the model it may be
 950//   better to give it a tag that is not a remove tag or a keep tag.  Such an object *will* be subject to
 951//   subtractions from other remove-tagged objects.
 952//   .
 953//   Note that `diff()` invokes its children three times.
 954//   .
 955//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 956// Arguments:
 957//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
 958//   keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed.  Default: `"keep"`
 959// Example: Diffing using default tags
 960//   diff()
 961//   cuboid(50) {
 962//       tag("remove") attach(TOP) sphere(d=40);
 963//       tag("keep") attach(CTR) cylinder(h=40, d=10);
 964//   }
 965// Example: The "hole" items are subtracted from everything else.  The other tags can be anything you find convenient.
 966//   diff("hole")
 967//     tag("body")sphere(d=100) {
 968//       tag("pole") zcyl(d=55, h=100);  // attach() not needed for center-to-center.
 969//       tag("hole") {
 970//          xcyl(d=55, h=101);
 971//          ycyl(d=55, h=101);
 972//       }
 973//       tag("axle")zcyl(d=15, h=140);
 974//     }
 975// Example:
 976//   diff(keep="axle")
 977//   sphere(d=100) {
 978//       tag("axle")xcyl(d=40, l=120);
 979//       tag("remove")cuboid([40,120,100]);
 980//   }
 981// Example: Masking
 982//   diff()
 983//   cube([80,90,100], center=true) {
 984//       edge_mask(FWD)
 985//           rounding_edge_mask(l=max($parent_size)*1.01, r=25);
 986//   }
 987// Example: Here we subtract the parent object from the child.  Because tags propagate to children we need to clear the "remove" tag from the child.
 988//  diff()
 989//     tag("remove")cuboid(10)
 990//       tag("")position(RIGHT+BACK)cyl(r=8,h=9);
 991// Example(3D,VPR=[104,0,200], VPT=[-0.9,3.03, -0.74], VPD=19,NoAxes,NoScales): A pipe module that subtracts its interior when you call it using diff().  Normally if you union two pipes together, you'll get interfering walls at the intersection, but not here:
 992//   $fn=16;
 993//   // This module must be called by subtracting with "diff"
 994//   module pipe(length, od, id) {
 995//       // Strip the tag the user is using to subtract
 996//       tag("")cylinder(h=length, d=od, center=true);
 997//       // Leave the tag alone here, so this one is removed
 998//       cylinder(h=length+.02, d=id, center=true);
 999//   }
1000//   // Draw some intersecting pipes
1001//   diff(){
1002//     tag("remove"){
1003//       pipe(length=5, od=2, id=1.9);
1004//       zrot(10)xrot(75)
1005//         pipe(length=5, od=2, id=1.9);
1006//     }
1007//     // The orange bar has its center removed
1008//     color("orange") down(1) xcyl(h=8, d=1);
1009//     // "keep" preserves the interior of the blue bar intact
1010//     tag("keep") recolor("blue") up(1) xcyl(h=8, d=1);
1011//   }
1012//   // Objects outside the diff don't have pipe interiors removed
1013//   color("purple") down(2.2) ycyl(h=8, d=0.3);
1014// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
1015//   $fn=32;
1016//   diff("rem1")
1017//   cyl(r=10,h=10){
1018//     diff("rem2",$tag="rem1"){
1019//       cyl(r=8,h=11);
1020//       tag("rem2")diff("rem3"){
1021//           cyl(r=6,h=12);
1022//           tag("rem3")cyl(r=4,h=13);
1023//           }
1024//       }
1025//   }
1026// Example: This example shows deep nesting, where all the differences cross levels.  Unlike the preceding example, each cylinder is positioned relative to its parent.  Note that it suffices to use two remove tags, alternating between them at each level.
1027//   $fn=32;
1028//   diff("remA")
1029//     cyl(r=9, h=6)
1030//       tag("remA")diff("remB")
1031//         left(.2)position(RIGHT)cyl(r=8,h=7,anchor=RIGHT)
1032//           tag("remB")diff("remA")
1033//            left(.2)position(LEFT)cyl(r=7,h=7,anchor=LEFT)
1034//              tag("remA")diff("remB")
1035//                left(.2)position(LEFT)cyl(r=6,h=8,anchor=LEFT)
1036//                  tag("remB")diff("remA")
1037//                    right(.2)position(RIGHT)cyl(r=5,h=9,anchor=RIGHT)
1038//                      tag("remA")diff("remB")
1039//                        right(.2)position(RIGHT)cyl(r=4,h=10,anchor=RIGHT)
1040//                          tag("remB")left(.2)position(LEFT)cyl(r=3,h=11,anchor=LEFT);
1041// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.
1042//   back_half()
1043//     diff("remove")
1044//       cuboid(40) {
1045//         attach(TOP)
1046//           recolor("lightgreen")
1047//             cyl(l=10,d=30);
1048//         position(TOP+RIGHT)
1049//           force_tag("remove")
1050//             xrot(90)
1051//               rotate_extrude()
1052//                 right(20)
1053//                   circle(5);
1054//       }
1055// Example: Here is another example where two children are intersected using the native intersection operator, and then tagged with {{force_tag()}}.  Note that because the children are at the same level, you don't need to use a tagged operator for their intersection.
1056//  $fn=32;
1057//  diff()
1058//    cuboid(10){
1059//      force_tag("remove")intersection()
1060//        {
1061//          position(RIGHT) cyl(r=7,h=15);
1062//          position(LEFT) cyl(r=7,h=15);
1063//        }
1064//      tag("keep")cyl(r=1,h=9);
1065//    }
1066// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
1067//   $fn=32;
1068//   diff()
1069//     cuboid(10){
1070//       tag("remove")cyl(r=4,h=11)
1071//         tag("keep")cyl(r=3,h=17)
1072//           tag("remove")position(RIGHT)cyl(r=2,h=18);
1073//     }
1074// Example: Combining tag operators can be tricky.  Here the `diff()` operation keeps two tags, "fullkeep" and "keep".  Then {{intersect()}} intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.
1075//   $fn=32;
1076//   intersect("keep","fullkeep")
1077//     diff(keep="fullkeep keep")
1078//       cuboid(10){
1079//         tag("remove")cyl(r=4,h=11);
1080//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1081//         tag("fullkeep")cyl(r=1,h=12);
1082//     }
1083// Example: In this complex example we form an intersection, subtract an object, and keep some objects.  Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect.  This is because without a tag they become part of the intersection and the result ends up the same.  For the two cylinders at the back, however, the result is different.  With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
1084//   $fn=64;
1085//   diff()
1086//     intersect(keep="remove keep")
1087//       cuboid(10,$thing="cube"){
1088//         tag("intersect"){
1089//           position(RIGHT) cyl(r=5.5,h=15)
1090//              tag("")cyl(r=2,h=10);
1091//           position(LEFT) cyl(r=5.54,h=15)
1092//              tag("keep")cyl(r=2,h=10);
1093//         }
1094//         // Untagged it is in the intersection
1095//         tag("") position(BACK+RIGHT)
1096//           cyl(r=2,h=10,anchor=CTR);
1097//         // With keep the full cylinder appears
1098//         tag("keep") position(BACK+LEFT)
1099//           cyl(r=2,h=10,anchor=CTR);
1100//         tag("remove") cyl(r=3,h=15);
1101//       }
1102module diff(remove="remove", keep="keep")
1103{
1104    req_children($children);
1105    assert(is_string(remove),"remove must be a string of tags");
1106    assert(is_string(keep),"keep must be a string of tags");
1107    if (_is_shown())
1108    {
1109        difference() {
1110            hide(str(remove," ",keep)) children();
1111            show_only(remove) children();
1112        }
1113    }
1114    show_int(keep)children();
1115}
1116
1117
1118// Module: tag_diff()
1119// Synopsis: Performs a {{diff()}} and then sets a tag on the result.
1120// Topics: Attachments
1121// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect()
1122// Usage:
1123//   tag_diff(tag, [remove], [keep]) PARENT() CHILDREN;
1124// Description:
1125//   Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
1126//   and then tag the resulting difference object with the specified tag.  This forces the specified
1127//   tag to be resolved at the level of the difference operation.  In most cases, this is not necessary,
1128//   but if you have kept objects and want to operate on this difference object as a whole object using
1129//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1130//   .
1131//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1132// Arguments:
1133//   tag = Tag string to apply to this difference object
1134//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
1135//   keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed.  Default: `"keep"`
1136// Side Effects:
1137//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1138// Example: In this example we have a difference with a kept object that is then subtracted from a cube, but we don't want the kept object to appear in the final output, so this result is wrong:
1139//   diff("rem"){
1140//     cuboid([20,10,30],anchor=FRONT);
1141//     tag("rem")diff("remove","keep"){
1142//       cuboid([10,10,20]);
1143//       tag("remove")cuboid([11,11,5]);
1144//       tag("keep")cuboid([2,2,20]);
1145//     }
1146//   }
1147// Example: Using tag_diff corrects the problem:
1148//   diff("rem"){
1149//     cuboid([20,10,30],anchor=FRONT);
1150//       tag_diff("rem","remove","keep"){
1151//         cuboid([10,10,20]);
1152//         tag("remove")cuboid([11,11,5]);
1153//         tag("keep")cuboid([2,2,20]);
1154//       }
1155//   }
1156// Example: This concentric cylinder example uses "keep" and produces the wrong result.  The kept cylinder gets kept in the final output instead of subtracted.  This happens even when we make sure to change the `keep` argument at the top level {{diff()}} call.
1157//   diff("rem","nothing")
1158//     cyl(r=8,h=6)
1159//       tag("rem")diff()
1160//         cyl(r=7,h=7)
1161//           tag("remove")cyl(r=6,h=8)
1162//           tag("keep")cyl(r=5,h=9);
1163// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
1164//   diff("rem")
1165//     cyl(r=8,h=6)
1166//       tag_diff("rem")
1167//         cyl(r=7,h=7)
1168//           tag("remove")cyl(r=6,h=8)
1169//           tag("keep")cyl(r=5,h=9);
1170module tag_diff(tag,remove="remove", keep="keep")
1171{
1172    req_children($children);
1173    assert(is_string(remove),"remove must be a string of tags");
1174    assert(is_string(keep),"keep must be a string of tags");
1175    assert(is_string(tag),"tag must be a string");
1176    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1177    $tag=str($tag_prefix,tag);
1178    if (_is_shown())
1179      show_all(){
1180         difference() {
1181            hide(str(remove," ",keep)) children();
1182            show_only(remove) children();
1183         }
1184         show_only(keep)children();
1185      }
1186}
1187
1188
1189// Module: intersect()
1190// Synopsis: Perform an intersection operation on children using tags rather than hierarchy to control what happens.
1191// Topics: Attachments
1192// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), tag_intersect()
1193// Usage:
1194//   intersect([intersect], [keep]) PARENT() CHILDREN;
1195// Description:
1196//   Performs an intersection operation on its children, using tags to
1197//   determine what happens.  This is specifically intended to address
1198//   the situation where you want intersections involving a parent and
1199//   child object, something that is impossible with the native
1200//   intersection() module.  This module treats the children in three
1201//   groups: objects matching the tags listed in `intersect`, objects
1202//   matching tags listed in `keep`, and the remaining objects that
1203//   don't match any of the listed tags.  The intersection is computed
1204//   between the union of the `intersect` tagged objects and union of the objects that don't
1205//   match any of the listed tags.  Finally the objects listed in `keep` are
1206//   unioned with the result.  Attachable objects should be tagged using {{tag()}}
1207//   and non-attachable objects with {{force_tag()}}.
1208//   .
1209//   Note that `intersect()` invokes its children three times.
1210//   .
1211//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1212// Arguments:
1213//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1214//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1215// Example:
1216//   intersect("mask", keep="axle")
1217//     sphere(d=100) {
1218//         tag("mask")cuboid([40,100,100]);
1219//         tag("axle")xcyl(d=40, l=100);
1220//     }
1221// Example: Combining tag operators can be tricky.  Here the {{diff()}} operation keeps two tags, "fullkeep" and "keep".  Then `intersect()` intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.
1222//   $fn=32;
1223//   intersect("keep","fullkeep")
1224//     diff(keep="fullkeep keep")
1225//       cuboid(10){
1226//         tag("remove")cyl(r=4,h=11);
1227//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1228//         tag("fullkeep")cyl(r=1,h=12);
1229//     }
1230// Example: In this complex example we form an intersection, subtract an object, and keep some objects.  Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect.  This is because without a tag they become part of the intersection and the result ends up the same.  For the two cylinders at the back, however, the result is different.  With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
1231//   $fn=64;
1232//   diff()
1233//     intersect(keep="remove keep")
1234//       cuboid(10,$thing="cube"){
1235//         tag("intersect"){
1236//           position(RIGHT) cyl(r=5.5,h=15)
1237//              tag("")cyl(r=2,h=10);
1238//           position(LEFT) cyl(r=5.54,h=15)
1239//              tag("keep")cyl(r=2,h=10);
1240//         }
1241//         // Untagged it is in the intersection
1242//         tag("") position(BACK+RIGHT)
1243//           cyl(r=2,h=10,anchor=CTR);
1244//         // With keep the full cylinder appears
1245//         tag("keep") position(BACK+LEFT)
1246//           cyl(r=2,h=10,anchor=CTR);
1247//         tag("remove") cyl(r=3,h=15);
1248//       }
1249module intersect(intersect="intersect",keep="keep")
1250{
1251   assert(is_string(intersect),"intersect must be a string of tags");
1252   assert(is_string(keep),"keep must be a string of tags");
1253   intersection(){
1254      show_only(intersect) children();
1255      hide(str(intersect," ",keep)) children();
1256   }
1257   show_int(keep) children();
1258}
1259
1260
1261// Module: tag_intersect()
1262// Synopsis: Performs an {{intersect()}} and then tags the result.
1263// Topics: Attachments
1264// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), intersect()
1265// Usage:
1266//   tag_intersect(tag, [intersect], [keep]) PARENT() CHILDREN;
1267// Description:
1268//   Perform an intersection operation in the manner of {{intersect()}} using tags to control what happens,
1269//   and then tag the resulting difference object with the specified tag.  This forces the specified
1270//   tag to be resolved at the level of the intersect operation.  In most cases, this is not necessary,
1271//   but if you have kept objects and want to operate on this difference object as a whole object using
1272//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1273//   .
1274//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1275// Arguments:
1276//   tag = Tag to set for the intersection
1277//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1278//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1279// Side Effects:
1280//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1281// Example:  Without `tag_intersect()` the kept object is not included in the difference.
1282//   $fn=32;
1283//   diff()
1284//     cuboid([20,15,9])
1285//     tag("remove")intersect()
1286//       cuboid(10){
1287//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1288//         tag("keep")position(LEFT)cyl(r=4,h=10);
1289//       }
1290// Example: Using tag_intersect corrects the problem.
1291//   $fn=32;
1292//   diff()
1293//     cuboid([20,15,9])
1294//     tag_intersect("remove")
1295//       cuboid(10){
1296//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1297//         tag("keep")position(LEFT)cyl(r=4,h=10);
1298//       }
1299module tag_intersect(tag,intersect="intersect",keep="keep")
1300{
1301   assert(is_string(intersect),"intersect must be a string of tags");
1302   assert(is_string(keep),"keep must be a string of tags");
1303   assert(is_string(tag),"tag must be a string");
1304   assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1305   $tag=str($tag_prefix,tag);
1306   if (_is_shown())
1307     show_all(){
1308       intersection(){
1309          show_only(intersect) children();
1310          hide(str(intersect," ",keep)) children();
1311       }
1312       show_only(keep) children();
1313   }
1314}
1315
1316
1317// Module: conv_hull()
1318// Synopsis:  Performs a hull operation on the children using tags to determine what happens.
1319// Topics: Attachments, Hulling
1320// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect(), hull()
1321// Usage:
1322//   conv_hull([keep]) CHILDREN;
1323// Description:
1324//   Performs a hull operation on the children using tags to determine what happens.  The items
1325//   not tagged with the `keep` tags are combined into a convex hull, and the children tagged with the keep tags
1326//   are unioned with the result.
1327//   .
1328//   Note that `conv_hull()` invokes its children twice.  
1329//   .
1330//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1331// Arguments:
1332//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1333// Example:
1334//   conv_hull("keep")
1335//      sphere(d=100, $fn=64) {
1336//        cuboid([40,90,90]);
1337//        tag("keep")xcyl(d=40, l=120);
1338//      }
1339// Example: difference combined with hull where all objects are relative to each other.
1340//   $fn=32;
1341//   diff()
1342//     conv_hull("remove")
1343//       cuboid(10)
1344//         position(RIGHT+BACK)cyl(r=4,h=10)
1345//           tag("remove")cyl(r=2,h=12);
1346module conv_hull(keep="keep")
1347{
1348    req_children($children);
1349    assert(is_string(keep),"keep must be a string of tags");
1350    if (_is_shown())
1351        hull() hide(keep) children();
1352    show_int(keep) children();
1353}
1354
1355
1356// Module: tag_conv_hull()
1357// Synopsis: Performs a {{conv_hull()}} and then sets a tag on the result.
1358// Topics: Attachments
1359// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1360// Usage:
1361//   tag_conv_hull(tag, [keep]) CHILDREN;
1362// Description:
1363//   Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens,
1364//   and then tag the resulting hull object with the specified tag.  This forces the specified
1365//   tag to be resolved at the level of the hull operation.  In most cases, this is not necessary,
1366//   but if you have kept objects and want to operate on the hull object as a whole object using
1367//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1368//   .
1369//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1370// Arguments:
1371//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1372// Side Effects:
1373//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1374// Example: With a regular tag, the kept object is not handled as desired:
1375//   diff(){
1376//      cuboid([30,30,9])
1377//        tag("remove")conv_hull("remove")
1378//          cuboid(10,anchor=LEFT+FRONT){
1379//            position(RIGHT+BACK)cyl(r=4,h=10);
1380//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1381//          }
1382//   }
1383// Example: Using `tag_conv_hull()` fixes the problem:
1384//   diff(){
1385//      cuboid([30,30,9])
1386//        tag_conv_hull("remove")
1387//          cuboid(10,anchor=LEFT+FRONT){
1388//            position(RIGHT+BACK)cyl(r=4,h=10);
1389//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1390//          }
1391//   }
1392module tag_conv_hull(tag,keep="keep")
1393{
1394    req_children($children);
1395    assert(is_string(keep),"keep must be a string of tags");
1396    assert(is_string(tag),"tag must be a string");
1397    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1398    $tag=str($tag_prefix,tag);
1399    if (_is_shown())
1400      show_all(){
1401        hull() hide(keep) children();
1402        show_only(keep) children();
1403      }
1404}
1405
1406
1407// Module: hide()
1408// Synopsis: Hides attachable children with the given tags.
1409// Topics: Attachments
1410// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
1411// Usage:
1412//   hide(tags) CHILDREN;
1413// Description:
1414//   Hides all attachable children with the given tags, which you supply as a space separated string. Previously hidden objects remain hidden, so hiding is cumulative, unlike `show_only()`.
1415//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1416// Side Effects:
1417//   Sets `$tags_hidden` to include the tags you specify.
1418// Example:  Hides part of the model.
1419//   hide("A")
1420//     tag("main") cube(50, anchor=CENTER, $tag="Main") {
1421//       tag("A")attach(LEFT, BOTTOM) cylinder(d=30, h=30);
1422//       tag("B")attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
1423//     }
1424// Example: Use an invisible parent to position children.  Note that children must be retagged because they inherit the parent tag.
1425//   $fn=16;
1426//   hide("hidden")
1427//     tag("hidden")cuboid(10)
1428//       tag("visible") {
1429//         position(RIGHT) cyl(r=1,h=12);
1430//         position(LEFT) cyl(r=1,h=12);
1431//       }
1432module hide(tags)
1433{
1434    req_children($children);
1435    dummy=assert(is_string(tags), "tags must be a string");
1436    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1437    $tags_hidden = concat($tags_hidden,taglist);
1438    children();
1439}
1440
1441
1442// Module: show_only()
1443// Synopsis: Show only the children with the listed tags.
1444// See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect()
1445// Topics: Attachments
1446// Usage:
1447//   show_only(tags) CHILDREN;
1448// Description:
1449//   Show only the children with the listed tags, which you sply as a space separated string.  Only unhidden objects will be shown, so if an object is hidden either before or after the `show_only()` call then it will remain hidden.  This overrides any previous `show_only()` calls.  Unlike `hide()`, calls to `show_only()` are not cumulative.
1450//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1451// Side Effects:
1452//   Sets `$tags_shown` to the tag you specify.
1453// Example:  Display the attachments but not the parent
1454//   show_only("visible")
1455//     cube(50, anchor=CENTER)
1456//       tag("visible"){
1457//         attach(LEFT, BOTTOM) cylinder(d=30, h=30);
1458//         attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
1459//       }
1460module show_only(tags)
1461{
1462    req_children($children);
1463    dummy=assert(is_string(tags), str("tags must be a string",tags));
1464    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1465    $tags_shown = taglist;
1466    children();
1467}
1468
1469// Module: show_all()
1470// Synopsis: Shows all children and clears tags.
1471// See Also: tag(), recolor(), show_only(), show_int(), diff(), intersect()
1472// Topics: Attachments
1473// Usage;
1474//   show_all() CHILDREN;
1475// Description:
1476//   Shows all children.  Clears the list of hidden tags and shown tags so that all child objects will be
1477//   fully displayed.
1478// Side Effects:
1479//   Sets `$tags_shown="ALL"`
1480//   Sets `$tags_hidden=[]`
1481module show_all()
1482{
1483   req_children($children);
1484   $tags_shown="ALL";
1485   $tags_hidden=[];
1486   children();
1487}
1488
1489
1490// Module: show_int()
1491// Synopsis: Shows children with the listed tags which were already shown in the parent context.
1492// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
1493// Topics: Attachments
1494// Usage:
1495//   show_int(tags) CHILDREN;
1496// Description:
1497//   Show only the children with the listed tags which were already shown in the parent context.
1498//   This intersects the current show list with the list of tags you provide.
1499// Arguments:
1500//   tags = list of tags to show
1501// Side Effects:
1502//   Sets `$tags_shown`
1503module show_int(tags)
1504{
1505    req_children($children);
1506    dummy=assert(is_string(tags), str("tags must be a string",tags));
1507    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1508    $tags_shown = $tags_shown == "ALL" ? taglist : set_intersection($tags_shown,taglist);
1509    children();
1510}
1511
1512
1513// Section: Mask Attachment
1514
1515
1516// Module: face_mask()
1517// Synopsis: Ataches a 3d mask shape to the given faces of the parent.
1518// SynTags: Trans
1519// Topics: Attachments, Masking
1520// See Also: attachable(), position(), attach(), edge_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile()
1521// Usage:
1522//   PARENT() face_mask(faces) CHILDREN;
1523// Description:
1524//   Takes a 3D mask shape, and attaches it to the given faces, with the appropriate orientation to be
1525//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the bottom half
1526//   (Z-) shaped to be diffed away from the face of parent attachable shape.  If no tag is set then
1527//   `face_mask()` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1528//   For details on specifying the faces to mask see [Specifying Faces](attachments.scad#subsection-specifying-faces).
1529//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1530// Arguments:
1531//   edges = Faces to mask.  See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.  Default: All faces
1532// Side Effects:
1533//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1534//   `$idx` is set to the index number of each face in the list of faces given.
1535//   `$attach_anchor` is set for each face given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1536// Example:
1537//   diff()
1538//   cylinder(r=30, h=60)
1539//       face_mask(TOP) {
1540//           rounding_cylinder_mask(r=30,rounding=5);
1541//           cuboid([5,61,10]);
1542//       }
1543// Example: Using `$idx`
1544//   diff()
1545//   cylinder(r=30, h=60)
1546//       face_mask([TOP, BOT])
1547//           zrot(45*$idx) zrot_copies([0,90]) cuboid([5,61,10]);
1548module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) {
1549    req_children($children);
1550    faces = is_vector(faces)? [faces] : faces;
1551    assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face.");
1552    assert($parent_geom != undef, "No object to attach to!");
1553    attach(faces) {
1554       default_tag("remove") children();
1555    }
1556}
1557
1558
1559// Module: edge_mask()
1560// Synopsis: Attaches a 3D mask shape to the given edges of the parent.
1561// SynTags: Trans
1562// Topics: Attachments, Masking
1563// See Also: attachable(), position(), attach(), face_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile()
1564// Usage:
1565//   PARENT() edge_mask([edges], [except]) CHILDREN;
1566// Description:
1567//   Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
1568//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the back-right
1569//   quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape.  If no tag is set
1570//   then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1571//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1572//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1573// Figure: A Typical Edge Rounding Mask
1574//   module roundit(l,r) difference() {
1575//       translate([-1,-1,-l/2])
1576//           cube([r+1,r+1,l]);
1577//       translate([r,r])
1578//           cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1579//   }
1580//   roundit(l=30,r=10);
1581// Arguments:
1582//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1583//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1584// Side Effects:
1585//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1586//   `$idx` is set to the index number of each edge.
1587//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1588//   `$parent_size` is set to the size of the parent object.
1589// Example:
1590//   diff()
1591//   cube([50,60,70],center=true)
1592//       edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
1593//           rounding_edge_mask(l=71,r=10);
1594module edge_mask(edges=EDGES_ALL, except=[]) {
1595    req_children($children);
1596    assert($parent_geom != undef, "No object to attach to!");
1597    edges = _edges(edges, except=except);
1598    vecs = [
1599        for (i = [0:3], axis=[0:2])
1600        if (edges[axis][i]>0)
1601        EDGE_OFFSETS[axis][i]
1602    ];
1603    for ($idx = idx(vecs)) {
1604        vec = vecs[$idx];
1605        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1606        dummy=assert(vcount == 2, "Not an edge vector!");
1607        anch = _find_anchor(vec, $parent_geom);
1608        $attach_to = undef;
1609        $attach_anchor = anch;
1610        $attach_norot = true;
1611        rotang =
1612            vec.z<0? [90,0,180+v_theta(vec)] :
1613            vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1614            vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1615            [-90,0,180+v_theta(vec)];
1616        translate(anch[1]) rot(rotang)
1617           default_tag("remove") children();
1618    }
1619}
1620
1621
1622// Module: corner_mask()
1623// Synopsis: Attaches a 3d mask shape to the given corners of the parent.
1624// SynTags: Trans
1625// Topics: Attachments, Masking
1626// See Also: attachable(), position(), attach(), face_mask(), edge_mask(), face_profile(), edge_profile(), corner_profile()
1627// Usage:
1628//   PARENT() corner_mask([corners], [except]) CHILDREN;
1629// Description:
1630//   Takes a 3D mask shape, and attaches it to the specified corners, with the appropriate orientation to
1631//   be differenced away.  The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant.  If no tag is set
1632//   then `corner_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1633//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
1634//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1635// Arguments:
1636//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
1637//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
1638// Side Effects:
1639//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1640//   `$idx` is set to the index number of each corner.
1641//   `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1642// Example:
1643//   diff()
1644//   cube(100, center=true)
1645//       corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1646//           difference() {
1647//               translate(-0.01*[1,1,1]) cube(20);
1648//               translate([20,20,20]) sphere(r=20);
1649//           }
1650module corner_mask(corners=CORNERS_ALL, except=[]) {
1651    req_children($children);
1652    assert($parent_geom != undef, "No object to attach to!");
1653    corners = _corners(corners, except=except);
1654    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1655    for ($idx = idx(vecs)) {
1656        vec = vecs[$idx];
1657        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1658        dummy=assert(vcount == 3, "Not an edge vector!");
1659        anch = _find_anchor(vec, $parent_geom);
1660        $attach_to = undef;
1661        $attach_anchor = anch;
1662        $attach_norot = true;
1663        rotang = vec.z<0?
1664            [  0,0,180+v_theta(vec)-45] :
1665            [180,0,-90+v_theta(vec)-45];
1666        translate(anch[1]) rot(rotang)
1667            default_tag("remove") children();
1668    }
1669}
1670
1671
1672// Module: face_profile()
1673// Synopsis: Extrudes a 2D edge profile into a mask for all edges and corners of the given faces on the parent.
1674// SynTags: Geom
1675// Topics: Attachments, Masking
1676// See Also: attachable(), position(), attach(), edge_profile(), corner_profile(), face_mask(), edge_mask(), corner_mask()
1677// Usage:
1678//   PARENT() face_profile(faces, r|d=, [convexity=]) CHILDREN;
1679// Description:
1680//   Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. If no tag is set
1681//   then `face_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1682//   See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.
1683//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1684// Arguments:
1685//   faces = Faces to mask edges and corners of.
1686//   r = Radius of corner mask.
1687//   ---
1688//   d = Diameter of corner mask.
1689//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1690//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1691// Side Effects:
1692//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1693//   `$idx` is set to the index number of each face.
1694//   `$attach_anchor` is set for each edge or corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1695//   `$profile_type` is set to `"edge"` or `"corner"`, depending on what is being masked.
1696// Example:
1697//   diff()
1698//   cube([50,60,70],center=true)
1699//       face_profile(TOP,r=10)
1700//           mask2d_roundover(r=10);
1701module face_profile(faces=[], r, d, excess=0.01, convexity=10) {
1702    req_children($children);
1703    faces = is_vector(faces)? [faces] : faces;
1704    assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face.");
1705    r = get_radius(r=r, d=d, dflt=undef);
1706    assert(is_num(r) && r>=0);
1707    edge_profile(faces, excess=excess) children();
1708    corner_profile(faces, convexity=convexity, r=r) children();
1709}
1710
1711
1712// Module: edge_profile()
1713// Synopsis: Extrudes a 2d edge profile into a mask on the given edges of the parent.
1714// SynTags: Geom
1715// Topics: Attachments, Masking
1716// See Also: attachable(), position(), attach(), face_profile(), edge_profile_asym(), corner_profile(), edge_mask(), face_mask(), corner_mask()
1717// Usage:
1718//   PARENT() edge_profile([edges], [except], [convexity]) CHILDREN;
1719// Description:
1720//   Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and
1721//   extruded length to be `diff()`ed away, to give the edge a matching profile.  If no tag is set
1722//   then `edge_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1723//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1724//   For a step-by-step
1725//   explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1726// Arguments:
1727//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1728//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1729//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1730//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1731// Side Effects:
1732//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1733//   `$idx` is set to the index number of each edge.
1734//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1735//   `$profile_type` is set to `"edge"`.
1736//   `$edge_angle` is set to the inner angle of the current edge.
1737// Example:
1738//   diff()
1739//   cube([50,60,70],center=true)
1740//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
1741//           mask2d_roundover(r=10, inset=2);
1742// Example: Using $edge_angle on a Conoid
1743//   diff()
1744//   cyl(d1=50, d2=30, l=40, anchor=BOT) {
1745//       edge_profile([TOP,BOT], excess=10, convexity=6) {
1746//           mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle);
1747//       }
1748//   }
1749// Example: Using $edge_angle on a Prismoid
1750//   diff()
1751//   prismoid([60,50],[30,20],h=40,shift=[-25,15]) {
1752//       edge_profile(excess=10, convexity=20) {
1753//           mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle);
1754//       }
1755//   }
1756
1757module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) {
1758    req_children($children);
1759    check1 = assert($parent_geom != undef, "No object to attach to!");
1760    conoid = $parent_geom[0] == "conoid";
1761    edges = !conoid? _edges(edges, except=except) :
1762        edges==EDGES_ALL? [TOP,BOT] :
1763        assert(all([for (e=edges) in_list(e,[TOP,BOT])]), "Invalid conoid edge spec.")
1764        edges;
1765    vecs = conoid
1766      ? [for (e=edges) e+FWD]
1767      : [
1768            for (i = [0:3], axis=[0:2])
1769            if (edges[axis][i]>0)
1770            EDGE_OFFSETS[axis][i]
1771        ];
1772    all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]);
1773    check2 = assert(all_vecs_are_edges, "All vectors must be edges.");
1774    default_tag("remove")
1775    for ($idx = idx(vecs)) {
1776        vec = vecs[$idx];
1777        anch = _find_anchor(vec, $parent_geom);
1778        path_angs_T = _attach_geom_edge_path($parent_geom, vec);
1779        path = path_angs_T[0];
1780        vecs = path_angs_T[1];
1781        post_T = path_angs_T[2];
1782        $attach_to = undef;
1783        $attach_anchor = anch;
1784        $attach_norot = true;
1785        $profile_type = "edge";
1786        multmatrix(post_T) {
1787            for (i = idx(path,e=-2)) {
1788                pt1 = select(path,i);
1789                pt2 = select(path,i+1);
1790                cp = (pt1 + pt2) / 2;
1791                v1 = vecs[i][0];
1792                v2 = vecs[i][1];
1793                $edge_angle = 180 - vector_angle(v1,v2);
1794                if (!approx(pt1,pt2)) {
1795                    seglen = norm(pt2-pt1) + 2 * excess;
1796                    move(cp) {
1797                        frame_map(x=-v2, z=unit(pt2-pt1)) {
1798                            linear_extrude(height=seglen, center=true, convexity=convexity)
1799                                mirror([-1,1]) children();
1800                        }
1801                    }
1802                }
1803            }
1804        }
1805    }
1806}
1807
1808
1809// Module: edge_profile_asym()
1810// Synopsis: Extrudes an asymmetric 2D profile into a mask on the given edges and corners of the parent.
1811// SynTags: Geom
1812// Topics: Attachments, Masking
1813// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_profile(), edge_mask(), face_mask(), corner_mask()
1814// Usage:
1815//   PARENT() edge_profile([edges], [except=], [convexity=], [flip=], [corner_type=]) CHILDREN;
1816// Description:
1817//   Takes an asymmetric 2D mask shape and attaches it to the selected edges and corners, with the appropriate
1818//   orientation and extruded length to be `diff()`ed away, to give the edges and corners a matching profile.
1819//   If no tag is set then `edge_profile_asym()` sets the tag for children to "remove" so that it will work
1820//   with the default {{diff()}} tag.  For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1821//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1822//   .
1823//   The asymmetric profiles are joined consistently at the corners.  This is impossible if all three edges at a corner use the profile, hence
1824//   this situation is not permitted.  The profile orientation can be inverted using the `flip=true` parameter.
1825//   .
1826//   The standard profiles are located in the first quadrant and have positive X values.  If you provide a profile located in the second quadrant,
1827//   where the X values are negative, then it will produce a fillet.  You can flip any of the standard profiles using {{xflip()}}.  
1828//   Fillets are always asymmetric because at a given edge, they can blend in two different directions, so even for symmetric profiles,
1829//   the asymmetric logic is required.  You can set the `corner_type` parameter to select rounded, chamfered or sharp corners.
1830//   However, when the corners are inside (concave) corners, you must provide the size of the profile ([width,height]), because the
1831//   this information is required to produce the correct corner and cannot be obtain from the profile itself, which is a child object.  
1832// Arguments:
1833//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1834//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1835//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1836//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1837//   flip = If true, reverses the orientation of any external profile parts at each edge.  Default false
1838//   corner_type = Specifies how exterior corners should be formed.  Must be one of `"none"`, `"chamfer"`, `"round"`, or `"sharp"`.  Default: `"none"`
1839//   size = If given the width and height of the 2D profile, will enable rounding and chamfering of internal corners when given a negative profile.
1840// Side Effects:
1841//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1842//   `$idx` is set to the index number of each edge.
1843//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1844//   `$profile_type` is set to `"edge"`.
1845//   `$edge_angle` is set to the inner angle of the current edge.
1846// Example:
1847//   ogee = [
1848//       "xstep",1,  "ystep",1,  // Starting shoulder.
1849//       "fillet",5, "round",5,  // S-curve.
1850//       "ystep",1,  "xstep",1   // Ending shoulder.
1851//   ];
1852//   diff()
1853//   cuboid(50) {
1854//       edge_profile_asym(FRONT)
1855//          mask2d_ogee(ogee);
1856//   }
1857// Example: Flipped
1858//   ogee = [
1859//       "xstep",1,  "ystep",1,  // Starting shoulder.
1860//       "fillet",5, "round",5,  // S-curve.
1861//       "ystep",1,  "xstep",1   // Ending shoulder.
1862//   ];
1863//   diff()
1864//   cuboid(50) {
1865//       edge_profile_asym(FRONT, flip=true)
1866//          mask2d_ogee(ogee);
1867//   }
1868// Example: Negative Chamfering
1869//   cuboid(50) {
1870//       edge_profile_asym(FWD, flip=false)
1871//           xflip() mask2d_chamfer(10);
1872//       edge_profile_asym(BACK, flip=true, corner_type="sharp")
1873//           xflip() mask2d_chamfer(10);
1874//   }
1875// Example: Negative Roundings
1876//   cuboid(50) {
1877//       edge_profile_asym(FWD, flip=false)
1878//           xflip() mask2d_roundover(10);
1879//       edge_profile_asym(BACK, flip=true, corner_type="round")
1880//           xflip() mask2d_roundover(10);
1881//   }
1882// Example: Cornerless
1883//   cuboid(50) {
1884//       edge_profile_asym(
1885//           "ALL", except=[TOP+FWD+RIGHT, BOT+BACK+LEFT]
1886//        ) xflip() mask2d_roundover(10);
1887//   }
1888// Example: More complicated edge sets
1889//   cuboid(50) {
1890//       edge_profile_asym(
1891//           [FWD,BACK,BOT+RIGHT], except=[FWD+RIGHT,BOT+BACK],
1892//           corner_type="round"
1893//        ) xflip() mask2d_roundover(10);
1894//   }
1895// Example: Mixing it up a bit.
1896//   diff()
1897//   cuboid(60) {
1898//       tag("keep") edge_profile_asym(LEFT, flip=true, corner_type="chamfer")
1899//           xflip() mask2d_chamfer(10);
1900//       edge_profile_asym(RIGHT)
1901//           mask2d_roundover(10);
1902//   }
1903// Example: Chamfering internal corners.
1904//   cuboid(40) {
1905//       edge_profile_asym(
1906//           [FWD+DOWN,FWD+LEFT],
1907//           corner_type="chamfer", size=[10,10]/sqrt(2)
1908//        ) xflip() mask2d_chamfer(10);
1909//   }
1910// Example: Rounding internal corners.
1911//   cuboid(40) {
1912//       edge_profile_asym(
1913//           [FWD+DOWN,FWD+LEFT],
1914//           corner_type="round", size=[10,10]
1915//        ) xflip() mask2d_roundover(10);
1916//   }
1917
1918module edge_profile_asym(
1919    edges=EDGES_ALL, except=[],
1920    excess=0.01, convexity=10,
1921    flip=false, corner_type="none",
1922    size=[0,0]
1923) {
1924    function _corner_orientation(pos,pvec) =
1925        let(
1926            j = [for (i=[0:2]) if (pvec[i]) i][0],
1927            T = (pos.x>0? xflip() : ident(4)) *
1928                (pos.y>0? yflip() : ident(4)) *
1929                (pos.z>0? zflip() : ident(4)) *
1930                rot(-120*(2-j), v=[1,1,1])
1931        ) T;
1932
1933    function _default_edge_orientation(edge) =
1934        edge.z < 0? [[-edge.x,-edge.y,0], UP] :
1935        edge.z > 0? [[-edge.x,-edge.y,0], DOWN] :
1936        edge.y < 0? [[-edge.x,0,0], BACK] :
1937        [[-edge.x,0,0], FWD] ;
1938
1939    function _edge_transition_needs_flip(from,to) =
1940        let(
1941            flip_edges = [
1942                [BOT+FWD, [FWD+LEFT, FWD+RIGHT]],
1943                [BOT+BACK, [BACK+LEFT, BACK+RIGHT]],
1944                [BOT+LEFT, []],
1945                [BOT+RIGHT, []],
1946                [TOP+FWD, [FWD+LEFT, FWD+RIGHT]],
1947                [TOP+BACK, [BACK+LEFT, BACK+RIGHT]],
1948                [TOP+LEFT, []],
1949                [TOP+RIGHT, []],
1950                [FWD+LEFT, [TOP+FWD, BOT+FWD]],
1951                [FWD+RIGHT, [TOP+FWD, BOT+FWD]],
1952                [BACK+LEFT, [TOP+BACK, BOT+BACK]],
1953                [BACK+RIGHT, [TOP+BACK, BOT+BACK]],
1954            ],
1955            i = search([from], flip_edges, num_returns_per_match=1)[0],
1956            check = assert(i!=[], "Bad edge vector.")
1957        ) in_list(to,flip_edges[i][1]);
1958
1959    function _edge_corner_numbers(vec) =
1960        let(
1961            v2 = [for (i=idx(vec)) vec[i]? (vec[i]+1)/2*pow(2,i) : 0],
1962            off = v2.x + v2.y + v2.z,
1963            xs = [0, if (!vec.x) 1],
1964            ys = [0, if (!vec.y) 2],
1965            zs = [0, if (!vec.z) 4]
1966        ) [for (x=xs, y=ys, z=zs) x+y+z + off];
1967
1968    function _gather_contiguous_edges(edge_corners) =
1969        let(
1970            no_tri_corners = all([for(cn = [0:7]) len([for (ec=edge_corners) if(in_list(cn,ec[1])) 1])<3]),
1971            check = assert(no_tri_corners, "Cannot have three edges that meet at the same corner.")
1972        )
1973        _gather_contiguous_edges_r(
1974            [for (i=idx(edge_corners)) if(i) edge_corners[i]],
1975            edge_corners[0][1],
1976            [edge_corners[0][0]], []);
1977
1978    function _gather_contiguous_edges_r(edge_corners, ecns, curr, out) =
1979        len(edge_corners)==0? [each out, curr] :
1980        let(
1981            i1 = [
1982                for (i = idx(edge_corners))
1983                if (in_list(ecns[0], edge_corners[i][1]))
1984                i
1985            ],
1986            i2 = [
1987                for (i = idx(edge_corners))
1988                if (in_list(ecns[1], edge_corners[i][1]))
1989                i
1990            ]
1991        ) !i1 && !i2? _gather_contiguous_edges_r(
1992            [for (i=idx(edge_corners)) if(i) edge_corners[i]],
1993            edge_corners[0][1],
1994            [edge_corners[0][0]],
1995            [each out, curr]
1996        ) : let(
1997            nu_curr = [
1998                if (i1) edge_corners[i1[0]][0],
1999                each curr,
2000                if (i2) edge_corners[i2[0]][0],
2001            ],
2002            nu_ecns = [
2003                if (!i1) ecns[0] else [
2004                    for (ecn = edge_corners[i1[0]][1])
2005                    if (ecn != ecns[0]) ecn
2006                ][0],
2007                if (!i2) ecns[1] else [
2008                    for (ecn = edge_corners[i2[0]][1])
2009                    if (ecn != ecns[1]) ecn
2010                ][0],
2011            ],
2012            rem = [
2013                for (i = idx(edge_corners))
2014                if (i != i1[0] && i != i2[0])
2015                edge_corners[i]
2016            ]
2017        )
2018        _gather_contiguous_edges_r(rem, nu_ecns, nu_curr, out);
2019
2020    function _edge_transition_inversions(edge_string) =
2021        let(
2022            // boolean cumulative sum
2023            bcs = function(list, i=0, inv=false, out=[])
2024                    i>=len(list)? out :
2025                    let( nu_inv = list[i]? !inv : inv )
2026                    bcs(list, i+1, nu_inv, [each out, nu_inv]),
2027            inverts = bcs([
2028                false,
2029                for(i = idx(edge_string)) if (i)
2030                    _edge_transition_needs_flip(
2031                        edge_string[i-1],
2032                        edge_string[i]
2033                    )
2034            ]),
2035            boti = [for(i = idx(edge_string)) if (edge_string[i].z<0) i],
2036            topi = [for(i = idx(edge_string)) if (edge_string[i].z>0) i],
2037            lfti = [for(i = idx(edge_string)) if (edge_string[i].x<0) i],
2038            rgti = [for(i = idx(edge_string)) if (edge_string[i].x>0) i],
2039            idx = [for (m = [boti, topi, lfti, rgti]) if(m) m[0]][0],
2040            rinverts = inverts[idx] == false? inverts : [for (x = inverts) !x]
2041        ) rinverts;
2042
2043    function _is_closed_edge_loop(edge_string) =
2044        let(
2045            e1 = edge_string[0],
2046            e2 = last(edge_string)
2047        )
2048        len([for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) 1]) == 1 &&
2049        len([for (i=[0:2]) if (e1[i]==0 && abs(e2[i])==1) 1]) == 1 &&
2050        len([for (i=[0:2]) if (e2[i]==0 && abs(e1[i])==1) 1]) == 1;
2051
2052    function _edge_pair_perp_vec(e1,e2) =
2053        [for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) -e1[i] else 0];
2054
2055    req_children($children);
2056    check1 = assert($parent_geom != undef, "No object to attach to!")
2057        assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"]))
2058        assert(is_bool(flip));
2059    edges = _edges(edges, except=except);
2060    vecs = [
2061        for (i = [0:3], axis=[0:2])
2062        if (edges[axis][i]>0)
2063        EDGE_OFFSETS[axis][i]
2064    ];
2065    all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]);
2066    check2 = assert(all_vecs_are_edges, "All vectors must be edges.");
2067    edge_corners = [for (vec = vecs) [vec, _edge_corner_numbers(vec)]];
2068    edge_strings = _gather_contiguous_edges(edge_corners);
2069    default_tag("remove")
2070    for (edge_string = edge_strings) {
2071        inverts = _edge_transition_inversions(edge_string);
2072        flipverts = [for (x = inverts) flip? !x : x];
2073        vecpairs = [
2074            for (i = idx(edge_string))
2075            let (p = _default_edge_orientation(edge_string[i]))
2076            flipverts[i]? [p.y,p.x] : p
2077        ];
2078        is_loop = _is_closed_edge_loop(edge_string);
2079        for (i = idx(edge_string)) {
2080            if (corner_type!="none" && (i || is_loop)) {
2081                e1 = select(edge_string,i-1);
2082                e2 = select(edge_string,i);
2083                vp1 = select(vecpairs,i-1);
2084                vp2 = select(vecpairs,i);
2085                pvec = _edge_pair_perp_vec(e1,e2);
2086                pos = [for (i=[0:2]) e1[i]? e1[i] : e2[i]];
2087                mirT = _corner_orientation(pos, pvec);
2088                $attach_to = undef;
2089                $attach_anchor = _find_anchor(pos, $parent_geom);
2090                $attach_norot = true;
2091                $profile_type = "corner";
2092                position(pos) {
2093                    multmatrix(mirT) {
2094                        if (vp1.x == vp2.x && size.y > 0) {
2095                            zflip() {
2096                                if (corner_type=="chamfer") {
2097                                    fn = $fn;
2098                                    move([size.y,size.y]) {
2099                                        rotate_extrude(angle=90, $fn=4)
2100                                            left_half(planar=true, $fn=fn)
2101                                                zrot(-90) fwd(size.y) children();
2102                                    }
2103                                    linear_extrude(height=size.x) {
2104                                        mask2d_roundover(size.y, inset=0.01, $fn=4);
2105                                    }
2106                                } else if (corner_type=="round") {
2107                                    move([size.y,size.y]) {
2108                                        rotate_extrude(angle=90)
2109                                            left_half(planar=true)
2110                                                zrot(-90) fwd(size.y) children();
2111                                    }
2112                                    linear_extrude(height=size.x) {
2113                                        mask2d_roundover(size.y, inset=0.01);
2114                                    }
2115                                }
2116                            }
2117                        } else if (vp1.y == vp2.y) {
2118                            if (corner_type=="chamfer") {
2119                                fn = $fn;
2120                                rotate_extrude(angle=90, $fn=4)
2121                                    right_half(planar=true, $fn=fn)
2122                                        children();
2123                                rotate_extrude(angle=90, $fn=4)
2124                                    left_half(planar=true, $fn=fn)
2125                                        children();
2126                            } else if (corner_type=="round") {
2127                                rotate_extrude(angle=90)
2128                                    right_half(planar=true)
2129                                        children();
2130                                rotate_extrude(angle=90)
2131                                    left_half(planar=true)
2132                                        children();
2133                            } else { //corner_type == "sharp"
2134                                intersection() {
2135                                    rot([90,0, 0]) linear_extrude(height=100,center=true,convexity=convexity) children();
2136                                    rot([90,0,90]) linear_extrude(height=100,center=true,convexity=convexity) children();
2137                                }
2138                            }
2139                        }
2140                    }
2141                }
2142            }
2143        }
2144        for (i = idx(edge_string)) {
2145            $attach_to = undef;
2146            $attach_anchor = _find_anchor(edge_string[i], $parent_geom);
2147            $attach_norot = true;
2148            $profile_type = "edge";
2149            edge_profile(edge_string[i], excess=excess, convexity=convexity) {
2150                if (flipverts[i]) {
2151                    mirror([-1,1]) children();
2152                } else {
2153                    children();
2154                }
2155            }
2156        }
2157    }
2158}
2159
2160
2161
2162// Module: corner_profile()
2163// Synopsis: Rotationally extrudes a 2d edge profile into corner mask on the given corners of the parent.
2164// SynTags: Geom
2165// Topics: Attachments, Masking
2166// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask(), face_mask(), edge_mask()
2167// Usage:
2168//   PARENT() corner_profile([corners], [except], [r=|d=], [convexity=]) CHILDREN;
2169// Description:
2170//   Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
2171//   to the selected corners with the appropriate orientation. If no tag is set then `corner_profile()`
2172//   sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
2173//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
2174//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2175// Arguments:
2176//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
2177//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
2178//   ---
2179//   r = Radius of corner mask.
2180//   d = Diameter of corner mask.
2181//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
2182// Side Effects:
2183//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
2184//   `$idx` is set to the index number of each corner.
2185//   `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
2186//   `$profile_type` is set to `"corner"`.
2187// Example:
2188//   diff()
2189//   cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
2190//       corner_profile(TOP,r=10)
2191//           mask2d_teardrop(r=10, angle=40);
2192//   }
2193module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
2194    check1 = assert($parent_geom != undef, "No object to attach to!");
2195    r = max(0.01, get_radius(r=r, d=d, dflt=undef));
2196    check2 = assert(is_num(r), "Bad r/d argument.");
2197    corners = _corners(corners, except=except);
2198    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
2199    all_vecs_are_corners = all([for (vec = vecs) sum(v_abs(vec))==3]);
2200    check3 = assert(all_vecs_are_corners, "All vectors must be corners.");
2201    for ($idx = idx(vecs)) {
2202        vec = vecs[$idx];
2203        anch = _find_anchor(vec, $parent_geom);
2204        $attach_to = undef;
2205        $attach_anchor = anch;
2206        $attach_norot = true;
2207        $profile_type = "corner";
2208        rotang = vec.z<0?
2209            [  0,0,180+v_theta(vec)-45] :
2210            [180,0,-90+v_theta(vec)-45];
2211        default_tag("remove"){
2212            translate(anch[1]) {
2213                rot(rotang) {
2214                    down(0.01) {
2215                        linear_extrude(height=r+0.01, center=false) {
2216                            difference() {
2217                                translate(-[0.01,0.01]) square(r);
2218                                translate([r,r]) circle(r=r*0.999);
2219                            }
2220                        }
2221                    }
2222                    translate([r,r]) zrot(180) {
2223                        rotate_extrude(angle=90, convexity=convexity) {
2224                            right(r) xflip() {
2225                                children();
2226                            }
2227                        }
2228                    }
2229                }
2230            }
2231        }
2232    }
2233}
2234
2235
2236// Section: Making your objects attachable
2237
2238
2239// Module: attachable()
2240// Synopsis: Manages the anchoring, spin, orientation, and attachments for an object.
2241// Topics: Attachments
2242// See Also: reorient()
2243// Usage: Square/Trapezoid Geometry
2244//   attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], [override=], ...) {OBJECT; children();}
2245// Usage: Circle/Oval Geometry
2246//   attachable(anchor, spin, two_d=true, r=|d=, ...) {OBJECT; children();}
2247// Usage: 2D Path/Polygon Geometry
2248//   attachable(anchor, spin, two_d=true, path=, [extent=], ...) {OBJECT; children();}
2249// Usage: 2D Region Geometry
2250//   attachable(anchor, spin, two_d=true, region=, [extent=], ...) {OBJECT; children();}
2251// Usage: Cubical/Prismoidal Geometry
2252//   attachable(anchor, spin, [orient], size=, [size2=], [shift=], [override=],  ...) {OBJECT; children();}
2253// Usage: Cylindrical Geometry
2254//   attachable(anchor, spin, [orient], r=|d=, l=, [axis=], ...) {OBJECT; children();}
2255// Usage: Conical Geometry
2256//   attachable(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...) {OBJECT; children();}
2257// Usage: Spheroid/Ovoid Geometry
2258//   attachable(anchor, spin, [orient], r=|d=, ...) {OBJECT; children();}
2259// Usage: Extruded Path/Polygon Geometry
2260//   attachable(anchor, spin, path=, l=|h=, [extent=], ...) {OBJECT; children();}
2261// Usage: Extruded Region Geometry
2262//   attachable(anchor, spin, region=, l=|h=, [extent=], ...) {OBJECT; children();}
2263// Usage: VNF Geometry
2264//   attachable(anchor, spin, [orient], vnf=, [extent=], ...) {OBJECT; children();}
2265// Usage: Pre-Specified Geometry
2266//   attachable(anchor, spin, [orient], geom=) {OBJECT; children();}
2267//
2268// Description:
2269//   Manages the anchoring, spin, orientation, and attachments for OBJECT, located in a 3D volume or 2D area.
2270//   A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered.
2271//   A managed 2D area is just assumed to be centered.  The shape to be managed is given
2272//   as the first child to this module, and the second child should be given as `children()`.
2273//   For example, to manage a conical shape:
2274//   ```openscad
2275//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
2276//       cyl(r1=r1, r2=r2, l=h);
2277//       children();
2278//   }
2279//   ```
2280//   .
2281//   If this is *not* run as a child of `attach()` with the `to` argument
2282//   given, then the following transformations are performed in order:
2283//   * Translates so the `anchor` point is at the origin (0,0,0).
2284//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
2285//   * Rotates so the top of the part points towards the vector `orient`.
2286//   .
2287//   If this is called as a child of `attach(from,to)`, then the info
2288//   for the anchor points referred to by `from` and `to` are fetched,
2289//   which will include position, direction, and spin.  With that info,
2290//   the following transformations are performed:
2291//   * Translates this part so it's anchor position matches the parent's anchor position.
2292//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
2293//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
2294//   .
2295//   In addition to handling positioning of the attachable object, 
2296//   this module is also responsible for handing coloring of objects with {{recolor()}} and {{color_this()}}, and
2297//   it is responsible for processing tags and determining whether the object should
2298//   display or not in the current context.  The determination based on the tags of whether to display the attachable object
2299//   often occurs in this module, which means that an object which does not display (e.g. a "remove" tagged object
2300//   inside {{diff()}}) cannot have internal {{tag()}} calls that change its tags and cause submodel
2301//   portions to display: the entire object simply does not run.  If you want the use the attachable object's internal tags outside
2302//   of the attachable object you can set `expose_tags=true` which delays the determination to display objects to the children.
2303//   For this to work correctly, all of the children must be attachables.  An example situation where you should set
2304//   `expose_tags=true` is when you want to have negative space in an attachable object that gets removed from the parent via
2305//   a "remove" tagged component of your attachable.  
2306//   .
2307//   Application of {{recolor()}} and {{color_this()}} also happens in this module and normally it applies to the
2308//   entire attachable object, so coloring commands that you give internally in the first child to `attachable()` have no effect.
2309//   Generally it makes sense that if a user specifies a color for an attachable object, the entire object is displayed
2310//   in that color, but if you want to retain control of color for sub-parts of an attachable object, you can use
2311//   the `keep_color=true` option, which delays the assignment of colors to the child level.  For this to work
2312//   correctly, all of the sub-parts of your attachable object must be attachables.  Also note that this option could
2313//   be confusing to users who don't understand why color commands are not working on the object.  
2314//   .
2315//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2316//
2317// Arguments:
2318//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2319//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2320//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2321//   ---
2322//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2323//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
2324//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
2325//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2326//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2327//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2328//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2329//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2330//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2331//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
2332//   vnf = The [VNF](vnf.scad) of the volume.
2333//   path = The path to generate a polygon from.
2334//   region = The region to generate a shape from.
2335//   extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths.  Default: true.
2336//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2337//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2338//   anchors = If given as a list of anchor points, allows named anchor points.
2339//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2340//   axis = The vector pointing along the axis of a geometry.  Default: UP
2341//   override = Function that takes an anchor and for 3d returns a triple `[position, direction, spin]` or for 2d returns a pair `[position,direction]` to use for that anchor to override the normal one.  You can also supply a lookup table that is a list of `[anchor, [position, direction, spin]]` entries.  If the direction/position/spin that is returned is undef then the default will be used.  This option applies only to the "trapezoid" and "prismoid" geometry types.  
2342//   geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
2343//   expose_tags = If true then delay the decision to display or not display this object to the children, which it possible for tags to respond to operations like {{diff()}} used outside the attachble object.  Only works correctly if everything in the attachable is also attachable.  Default: false
2344//   keep_color = If true then delay application of color to the children, which means that externally applied color is overridden by color specified within the attachable.  Only works properly if everything in the attachable is also attacahble.  Default: false
2345//
2346// Side Effects:
2347//   `$parent_anchor` is set to the parent object's `anchor` value.
2348//   `$parent_spin` is set to the parent object's `spin` value.
2349//   `$parent_orient` is set to the parent object's `orient` value.
2350//   `$parent_geom` is set to the parent object's `geom` value.
2351//   `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
2352//   `$color` is used to set the color of the object
2353//   `$save_color` is used to revert color to the parent's color
2354//
2355// Example(NORENDER): Cubical Shape
2356//   attachable(anchor, spin, orient, size=size) {
2357//       cube(size, center=true);
2358//       children();
2359//   }
2360//
2361// Example(NORENDER): Prismoidal Shape
2362//   attachable(
2363//       anchor, spin, orient,
2364//       size=point3d(botsize,h),
2365//       size2=topsize,
2366//       shift=shift
2367//   ) {
2368//       prismoid(botsize, topsize, h=h, shift=shift);
2369//       children();
2370//   }
2371//
2372// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2373//   attachable(anchor, spin, orient, r=r, l=h) {
2374//       cyl(r=r, l=h);
2375//       children();
2376//   }
2377//
2378// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2379//   attachable(anchor, spin, orient, r=r, l=h, axis=BACK) {
2380//       cyl(r=r, l=h);
2381//       children();
2382//   }
2383//
2384// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2385//   attachable(anchor, spin, orient, r=r, l=h, axis=RIGHT) {
2386//       cyl(r=r, l=h);
2387//       children();
2388//   }
2389//
2390// Example(NORENDER): Conical Shape, Z-Axis Aligned
2391//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
2392//       cyl(r1=r1, r2=r2, l=h);
2393//       children();
2394//   }
2395//
2396// Example(NORENDER): Conical Shape, Y-Axis Aligned
2397//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=BACK) {
2398//       cyl(r1=r1, r2=r2, l=h);
2399//       children();
2400//   }
2401//
2402// Example(NORENDER): Conical Shape, X-Axis Aligned
2403//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=RIGHT) {
2404//       cyl(r1=r1, r2=r2, l=h);
2405//       children();
2406//   }
2407//
2408// Example(NORENDER): Spherical Shape
2409//   attachable(anchor, spin, orient, r=r) {
2410//       sphere(r=r);
2411//       children();
2412//   }
2413//
2414// Example(NORENDER): Extruded Polygon Shape, by Extents
2415//   attachable(anchor, spin, orient, path=path, l=length) {
2416//       linear_extrude(height=length, center=true)
2417//           polygon(path);
2418//       children();
2419//   }
2420//
2421// Example(NORENDER): Extruded Polygon Shape, by Intersection
2422//   attachable(anchor, spin, orient, path=path, l=length, extent=false) {
2423//       linear_extrude(height=length, center=true)
2424//           polygon(path);
2425//       children();
2426//   }
2427//
2428// Example(NORENDER): Arbitrary VNF Shape, by Extents
2429//   attachable(anchor, spin, orient, vnf=vnf) {
2430//       vnf_polyhedron(vnf);
2431//       children();
2432//   }
2433//
2434// Example(NORENDER): Arbitrary VNF Shape, by Intersection
2435//   attachable(anchor, spin, orient, vnf=vnf, extent=false) {
2436//       vnf_polyhedron(vnf);
2437//       children();
2438//   }
2439//
2440// Example(NORENDER): 2D Rectangular Shape
2441//   attachable(anchor, spin, orient, two_d=true, size=size) {
2442//       square(size, center=true);
2443//       children();
2444//   }
2445//
2446// Example(NORENDER): 2D Trapezoidal Shape
2447//   attachable(
2448//       anchor, spin, orient,
2449//       two_d=true,
2450//       size=[x1,y],
2451//       size2=x2,
2452//       shift=shift
2453//   ) {
2454//       trapezoid(w1=x1, w2=x2, h=y, shift=shift);
2455//       children();
2456//   }
2457//
2458// Example(NORENDER): 2D Circular Shape
2459//   attachable(anchor, spin, orient, two_d=true, r=r) {
2460//       circle(r=r);
2461//       children();
2462//   }
2463//
2464// Example(NORENDER): Arbitrary 2D Polygon Shape, by Extents
2465//   attachable(anchor, spin, orient, two_d=true, path=path) {
2466//       polygon(path);
2467//       children();
2468//   }
2469//
2470// Example(NORENDER): Arbitrary 2D Polygon Shape, by Intersection
2471//   attachable(anchor, spin, orient, two_d=true, path=path, extent=false) {
2472//       polygon(path);
2473//       children();
2474//   }
2475//
2476// Example(NORENDER): Using Pre-defined Geometry
2477//   geom = atype=="perim"? attach_geom(two_d=true, path=path, extent=false) :
2478//       atype=="extents"? attach_geom(two_d=true, path=path, extent=true) :
2479//       atype=="circle"? attach_geom(two_d=true, r=r) :
2480//       assert(false, "Bad atype");
2481//   attachable(anchor, spin, orient, geom=geom) {
2482//       polygon(path);
2483//       children();
2484//   }
2485//
2486// Example: An object can be designed to attach as negative space using {{diff()}}, but if you want an object to include both positive and negative space then you run into trouble because tags inside the `attachable()` are ignored.  One solution is to call attachable() twice.  This example shows how two calls to  attachable can create an object with positive and negative space.  Note, however, that children in the negative space are differenced away: the highlighted little cube does not survive into the final model.
2487//   module thing(anchor,spin,orient) {
2488//      tag("remove") attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient){
2489//        cuboid([10,10,16]);
2490//        union(){}   // dummy children
2491//      }
2492//      attachable(size=[15,15,15], anchor=anchor, spin=spin, orient=orient){
2493//        cuboid([15,15,15]);
2494//        children();
2495//      }
2496//   }
2497//   diff()
2498//     cube([19,10,19])
2499//       attach([FRONT],overlap=-4)
2500//         thing(anchor=TOP)
2501//           # attach(TOP) cuboid(2,anchor=TOP);
2502// Example: Here is an example where the "keep" tag allows children to appear in the negative space.  That tag is also needed for this module to produce the desired output.  As above, the tag must be applied outside the attachable() call.
2503//   module thing(anchor = CENTER, spin = 0, orient = UP) {
2504//      tag("remove") attachable(anchor, spin, orient, d1=0,d2=95,h=33) {
2505//          cylinder(h = 33.1, d1 = 0, d2 = 95, anchor=CENTER);
2506//          union(){}  // dummy children
2507//      }
2508//      tag("keep") attachable(anchor, spin, orient,d1=0,d2=95,h=33) {
2509//            cylinder(h = 33, d = 10,anchor=CENTER);
2510//            children();
2511//        }
2512//    }
2513//    diff()
2514//      cube(100)
2515//        attach([FRONT,TOP],overlap=-4)
2516//          thing(anchor=TOP)
2517//            tube(ir=12,h=10);
2518// Example: A different way to achieve similar effects to the above to examples is to use the `expose_tags` parameter.  This parameter allows you to use just one call to attachable.  The second example above can also be rewritten like this. 
2519//   module thing(anchor,spin,orient) {
2520//      attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient,expose_tags=true){
2521//        union(){
2522//          cuboid([15,15,15]);
2523//          tag("remove")cuboid([10,10,16]);
2524//        }
2525//        children();
2526//      }
2527//   }
2528//   diff()
2529//     cube([19,10,19])
2530//       attach([FRONT],overlap=-4)
2531//         thing(anchor=TOP);
2532// Example: An advantage of using `expose_tags` is that it can work on nested constructions.  Here the child cylinder is aligned relative to its parent and removed from the calling parent object.
2533//   $fn=64;
2534//   module thing(anchor=BOT){
2535//     attachable(anchor = anchor,d=9,h=6,expose_tags=true){
2536//       cyl(d = 9, h = 6) 
2537//         tag("remove") 
2538//            align(RIGHT+TOP,inside=true) 
2539//                 left(1)up(1)cyl(l=11, d=3);
2540//       children();
2541//     }
2542//   }
2543//   back_half()
2544//     diff()
2545//       cuboid(10)
2546//         position(TOP)thing(anchor=BOT);
2547// Example(3D,NoAxes): Here an attachable module uses {{recolor()}} to change the color of a sub-part, producing the result shown on the left.  But if the caller applies color to the attachable, then both the green and yellow are changed, as shown on the right.  
2548//   module thing(anchor=CENTER) {
2549//       attachable(anchor,size=[10,10,10]) {
2550//           cuboid(10)
2551//             position(TOP) recolor("green")
2552//               cuboid(5,anchor=BOT);
2553//           children();
2554//       }
2555//   }
2556//   move([-15,-15])
2557//   thing()
2558//     attach(RIGHT,BOT)
2559//       recolor("blue") cyl(d=5,h=5);
2560//   recolor("pink") thing()
2561//     attach(RIGHT,BOT)
2562//       recolor("blue") cyl(d=5,h=5);
2563// Example(3D,NoAxes): Using the `keep_color=true` option enables the green color to persist, even when the user specifies a color.
2564//   module thing(anchor=CENTER) {
2565//       attachable(anchor,size=[10,10,10],keep_color=true) {
2566//           cuboid(10)
2567//             position(TOP) recolor("green")
2568//               cuboid(5,anchor=BOT);
2569//           children();
2570//       }
2571//   }
2572//   recolor("pink") thing()
2573//     attach(RIGHT,BOT)
2574//       recolor("blue") cyl(d=5,h=5);
2575
2576module attachable(
2577    anchor, spin, orient,
2578    size, size2, shift,
2579    r,r1,r2, d,d1,d2, l,h,
2580    vnf, path, region,
2581    extent=true,
2582    cp=[0,0,0],
2583    offset=[0,0,0],
2584    anchors=[],
2585    two_d=false,
2586    axis=UP,override,
2587    geom,
2588    expose_tags=false, keep_color=false
2589) { 
2590    dummy1 =
2591        assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.")
2592        assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2593        assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2594        assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
2595    anchor = first_defined([$anchor_override, anchor, CENTER]);
2596    spin =   default(spin,   0);
2597    orient = is_def($anchor_override)? UP : default(orient, UP);
2598    region = !is_undef(region)? region :
2599        !is_undef(path)? [path] :
2600        undef;
2601    geom = is_def(geom)? geom :
2602        attach_geom(
2603            size=size, size2=size2, shift=shift,
2604            r=r, r1=r1, r2=r2, h=h,
2605            d=d, d1=d1, d2=d2, l=l,
2606            vnf=vnf, region=region, extent=extent,
2607            cp=cp, offset=offset, anchors=anchors,
2608            two_d=two_d, axis=axis, override=override
2609        );
2610    m = _attach_transform(anchor,spin,orient,geom);
2611    multmatrix(m) {
2612        $parent_anchor = anchor;
2613        $parent_spin   = spin;
2614        $parent_orient = orient;
2615        $parent_geom   = geom;
2616        $parent_size   = _attach_geom_size(geom);
2617        $attach_to   = undef;
2618        $anchor_override=undef;
2619        if (expose_tags || _is_shown()){
2620            if (!keep_color)
2621                _color($color) children(0);
2622            else {
2623                $save_color=undef; // Force color_this() color in effect to persist for the entire object
2624                children(0); 
2625            }
2626        }
2627        if (is_def($save_color)) {
2628            $color=$save_color;    // Revert to the color before color_this() call
2629            $save_color=undef;
2630            children(1);
2631        }
2632        else children(1);
2633    }
2634}
2635
2636// Function: reorient()
2637// Synopsis: Calculates the transformation matrix needed to reorient an object.
2638// SynTags: Trans, Path, VNF
2639// Topics: Attachments
2640// See Also: reorient(), attachable()
2641// Usage: Square/Trapezoid Geometry
2642//   mat = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], ...);
2643//   pts = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], p=, ...);
2644// Usage: Circle/Oval Geometry
2645//   mat = reorient(anchor, spin, [orient], two_d=true, r=|d=, ...);
2646//   pts = reorient(anchor, spin, [orient], two_d=true, r=|d=, p=, ...);
2647// Usage: 2D Path/Polygon Geometry
2648//   mat = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], ...);
2649//   pts = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], p=, ...);
2650// Usage: 2D Region/Polygon Geometry
2651//   mat = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], ...);
2652//   pts = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], p=, ...);
2653// Usage: Cubical/Prismoidal Geometry
2654//   mat = reorient(anchor, spin, [orient], size=, [size2=], [shift=], ...);
2655//   vnf = reorient(anchor, spin, [orient], size=, [size2=], [shift=], p=, ...);
2656// Usage: Cylindrical Geometry
2657//   mat = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], ...);
2658//   vnf = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], p=, ...);
2659// Usage: Conical Geometry
2660//   mat = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...);
2661//   vnf = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], p=, ...);
2662// Usage: Spheroid/Ovoid Geometry
2663//   mat = reorient(anchor, spin, [orient], r|d=, ...);
2664//   vnf = reorient(anchor, spin, [orient], r|d=, p=, ...);
2665// Usage: Extruded Path/Polygon Geometry
2666//   mat = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], ...);
2667//   vnf = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], p=, ...);
2668// Usage: Extruded Region Geometry
2669//   mat = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], ...);
2670//   vnf = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], p=, ...);
2671// Usage: VNF Geometry
2672//   mat = reorient(anchor, spin, [orient], vnf, [extent], ...);
2673//   vnf = reorient(anchor, spin, [orient], vnf, [extent], p=, ...);
2674//
2675// Description:
2676//   Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
2677//   the transformation matrix needed to be applied to the contents of that volume.  A managed 3D
2678//   volume is assumed to be vertically (Z-axis) oriented, and centered.  A managed 2D area is just
2679//   assumed to be centered.
2680//   .
2681//   If `p` is not given, then the transformation matrix will be returned.
2682//   If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
2683//   If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
2684//   If `p` contains a point, a new point will be returned, transformed by the matrix.
2685//   .
2686//   If `$attach_to` is not defined, then the following transformations are performed in order:
2687//   * Translates so the `anchor` point is at the origin (0,0,0).
2688//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
2689//   * Rotates so the top of the part points towards the vector `orient`.
2690//   .
2691//   If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
2692//   the following transformations are performed in order:
2693//   * Translates this part so it's anchor position matches the parent's anchor position.
2694//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
2695//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
2696//   .
2697//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2698//
2699// Arguments:
2700//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2701//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2702//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2703//   ---
2704//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2705//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
2706//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
2707//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2708//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2709//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2710//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2711//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2712//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2713//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
2714//   vnf = The [VNF](vnf.scad) of the volume.
2715//   path = The path to generate a polygon from.
2716//   region = The region to generate a shape from.
2717//   extent = If true, calculate anchors by extents, rather than intersection.  Default: false.
2718//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2719//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2720//   anchors = If given as a list of anchor points, allows named anchor points.
2721//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2722//   axis = The vector pointing along the axis of a geometry.  Default: UP
2723//   p = The VNF, path, or point to transform.
2724function reorient(
2725    anchor, spin, orient,
2726    size, size2, shift,
2727    r,r1,r2, d,d1,d2, l,h,
2728    vnf, path, region,
2729    extent=true,
2730    offset=[0,0,0],
2731    cp=[0,0,0],
2732    anchors=[],
2733    two_d=false,
2734    axis=UP, override, 
2735    geom,
2736    p=undef
2737) =
2738    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2739    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2740    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
2741    let(
2742        anchor = default(anchor, CENTER),
2743        spin =   default(spin,   0),
2744        orient = default(orient, UP),
2745        region = !is_undef(region)? region :
2746            !is_undef(path)? [path] :
2747            undef,
2748        geom = is_def(geom)? geom :
2749            attach_geom(
2750                size=size, size2=size2, shift=shift,
2751                r=r, r1=r1, r2=r2, h=h,
2752                d=d, d1=d1, d2=d2, l=l,
2753                vnf=vnf, region=region, extent=extent,
2754                cp=cp, offset=offset, anchors=anchors,
2755                two_d=two_d, axis=axis, override=override
2756            ),
2757        $attach_to = undef
2758    ) _attach_transform(anchor,spin,orient,geom,p);
2759
2760
2761// Function: named_anchor()
2762// Synopsis: Creates an anchor data structure.
2763// Topics: Attachments
2764// See Also: reorient(), attachable()
2765// Usage:
2766//   a = named_anchor(name, pos, [orient], [spin]);
2767//   a = named_anchor(name, [pos], rot=, [flip=]);
2768// Description:
2769//   Creates an anchor data structure.  You can specify the position, orient direction and spin directly.
2770//   Alternatively for the 3D case you can give a 4x4 rotation matrix which can specify the orient and spin, and optionally
2771//   the position, using a translation component of the matrix.  If you specify `pos` along with `rot` then the position you
2772//   give overrides any translation included in `rot`.  For a step-by-step explanation of attachments,
2773//   see the [Attachments Tutorial](Tutorial-Attachments).
2774// Arguments:
2775//   name = The string name of the anchor.  Lowercase.  Words separated by single dashes.  No spaces.
2776//   pos = The [X,Y,Z] position of the anchor.
2777//   orient = A vector pointing in the direction parts should project from the anchor position.  Default: UP
2778//   spin = If needed, the angle to rotate the part around the direction vector.  Default: 0
2779//   ---
2780//   rot = A 4x4 rotations matrix, which may include a translation
2781//   flip = If true, flip the anchor the opposite direction.  Default: false
2782function named_anchor(name, pos, orient, spin, rot, flip) =
2783  assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip")
2784  assert(num_defined([pos,rot])>0, "Must give pos or rot")
2785  is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0)]
2786 : 
2787  let(
2788      flip = default(flip,false),
2789      pos = default(pos,apply(rot,CTR)),
2790      rotpart = _force_rot(rot),
2791      dummy = assert(approx(det4(rotpart),1), "Input rotation is not a rotation matrix"),
2792      dir = flip ? apply(rotpart,DOWN)
2793                 : apply(rotpart,UP),
2794      rot = flip? affine3d_rot_by_axis(apply(rotpart,BACK),180)*rot
2795                      : rot,
2796      decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)),
2797      spin = decode[0]*sign(decode[1].z)
2798  )
2799  [name, pos, dir, spin];
2800  
2801
2802function _force_rot(T) =
2803   [for(i=[0:3])
2804       [for(j=[0:3]) j<3 ? T[i][j] :
2805                     i==3 ? 1
2806                       : 0]];
2807
2808// Function: attach_geom()
2809// Synopsis: Returns the internal geometry description of an attachable object.
2810// Topics: Attachments
2811// See Also: reorient(), attachable()
2812// Usage: Null/Point Geometry
2813//   geom = attach_geom(...);
2814// Usage: Square/Trapezoid Geometry
2815//   geom = attach_geom(two_d=true, size=, [size2=], [shift=], ...);
2816// Usage: Circle/Oval Geometry
2817//   geom = attach_geom(two_d=true, r=|d=, ...);
2818// Usage: 2D Path/Polygon/Region Geometry
2819//   geom = attach_geom(two_d=true, region=, [extent=], ...);
2820// Usage: Cubical/Prismoidal Geometry
2821//   geom = attach_geom(size=, [size2=], [shift=], ...);
2822// Usage: Cylindrical Geometry
2823//   geom = attach_geom(r=|d=, l=|h=, [axis=], ...);
2824// Usage: Conical Geometry
2825//   geom = attach_geom(r1|d1=, r2=|d2=, l=, [axis=], ...);
2826// Usage: Spheroid/Ovoid Geometry
2827//   geom = attach_geom(r=|d=, ...);
2828// Usage: Extruded 2D Path/Polygon/Region Geometry
2829//   geom = attach_geom(region=, l=|h=, [extent=], [shift=], [scale=], [twist=], ...);
2830// Usage: VNF Geometry
2831//   geom = attach_geom(vnf=, [extent=], ...);
2832//
2833// Description:
2834//   Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
2835//   This will probably not not ever need to be called by the end user.
2836//
2837// Arguments:
2838//   ---
2839//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2840//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
2841//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
2842//   scale = If given as number or a 2D vector, scales the top of the shape, relative to the bottom.  Default: `[1,1]`
2843//   twist = If given as number, rotates the top of the shape by the given number of degrees clockwise, relative to the bottom.  Default: `0`
2844//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2845//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2846//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2847//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2848//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2849//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2850//   l/h = Length of the cylindrical, conical or extruded region volume along axis.
2851//   vnf = The [VNF](vnf.scad) of the volume.
2852//   region = The region to generate a shape from.
2853//   extent = If true, calculate anchors by extents, rather than intersection.  Default: true.
2854//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2855//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2856//   anchors = If given as a list of anchor points, allows named anchor points.
2857//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2858//   axis = The vector pointing along the axis of a geometry.  Default: UP
2859//   override = Function that takes an anchor and returns a pair `[position,direction]` to use for that anchor to override the normal one.  You can also supply a lookup table that is a list of `[anchor, [position, direction]]` entries.  If the direction/position that is returned is undef then the default will be used.
2860//
2861// Example(NORENDER): Null/Point Shape
2862//   geom = attach_geom();
2863//
2864// Example(NORENDER): Cubical Shape
2865//   geom = attach_geom(size=size);
2866//
2867// Example(NORENDER): Prismoidal Shape
2868//   geom = attach_geom(
2869//       size=point3d(botsize,h),
2870//       size2=topsize, shift=shift
2871//   );
2872//
2873// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2874//   geom = attach_geom(r=r, h=h);
2875//
2876// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2877//   geom = attach_geom(r=r, h=h, axis=BACK);
2878//
2879// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2880//   geom = attach_geom(r=r, h=h, axis=RIGHT);
2881//
2882// Example(NORENDER): Conical Shape, Z-Axis Aligned
2883//   geom = attach_geom(r1=r1, r2=r2, h=h);
2884//
2885// Example(NORENDER): Conical Shape, Y-Axis Aligned
2886//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=BACK);
2887//
2888// Example(NORENDER): Conical Shape, X-Axis Aligned
2889//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=RIGHT);
2890//
2891// Example(NORENDER): Spherical Shape
2892//   geom = attach_geom(r=r);
2893//
2894// Example(NORENDER): Ovoid Shape
2895//   geom = attach_geom(r=[r_x, r_y, r_z]);
2896//
2897// Example(NORENDER): Arbitrary VNF Shape, Anchored by Extents
2898//   geom = attach_geom(vnf=vnf);
2899//
2900// Example(NORENDER): Arbitrary VNF Shape, Anchored by Intersection
2901//   geom = attach_geom(vnf=vnf, extent=false);
2902//
2903// Example(NORENDER): 2D Rectangular Shape
2904//   geom = attach_geom(two_d=true, size=size);
2905//
2906// Example(NORENDER): 2D Trapezoidal Shape
2907//   geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift, override=override);
2908//
2909// Example(NORENDER): 2D Circular Shape
2910//   geom = attach_geom(two_d=true, r=r);
2911//
2912// Example(NORENDER): 2D Oval Shape
2913//   geom = attach_geom(two_d=true, r=[r_x, r_y]);
2914//
2915// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Extents
2916//   geom = attach_geom(two_d=true, region=region);
2917//
2918// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Intersection
2919//   geom = attach_geom(two_d=true, region=region, extent=false);
2920//
2921// Example(NORENDER): Extruded Region, Anchored by Extents
2922//   geom = attach_geom(region=region, l=height);
2923//
2924// Example(NORENDER): Extruded Region, Anchored by Intersection
2925//   geom = attach_geom(region=region, l=length, extent=false);
2926//
2927
2928function _local_struct_val(struct, key)=
2929    assert(is_def(key),"key is missing")
2930    let(ind = search([key],struct)[0])
2931    ind == [] ? undef : struct[ind][1];
2932
2933
2934function attach_geom(
2935    size, size2,
2936    shift, scale, twist,
2937    r,r1,r2, d,d1,d2, l,h,
2938    vnf, region,
2939    extent=true,
2940    cp=[0,0,0],
2941    offset=[0,0,0],
2942    anchors=[],
2943    two_d=false,
2944    axis=UP, override
2945) =
2946    assert(is_bool(extent))
2947    assert(is_vector(cp) || is_string(cp))
2948    assert(is_vector(offset))
2949    assert(is_list(anchors))
2950    assert(is_bool(two_d))
2951    assert(is_vector(axis))
2952    !is_undef(size)? (
2953        let(
2954            over_f = is_undef(override) ? function(anchor) [undef,undef,undef]
2955                   : is_func(override) ? override
2956                   : function(anchor) _local_struct_val(override,anchor)
2957        )
2958        two_d? (
2959            let(
2960                size2 = default(size2, size.x),
2961                shift = default(shift, 0)
2962            )
2963            assert(is_vector(size,2))
2964            assert(is_num(size2))
2965            assert(is_num(shift))
2966            ["trapezoid", point2d(size), size2, shift, over_f, cp, offset, anchors]
2967        ) : (
2968            let(
2969                size2 = default(size2, point2d(size)),
2970                shift = default(shift, [0,0])
2971            )
2972            assert(is_vector(size,3))
2973            assert(is_vector(size2,2))
2974            assert(is_vector(shift,2))
2975            ["prismoid", size, size2, shift, axis, over_f, cp, offset, anchors]
2976        )
2977    ) : !is_undef(vnf)? (
2978        assert(is_vnf(vnf))
2979        assert(two_d == false)
2980        extent? ["vnf_extent", vnf, cp, offset, anchors] :
2981        ["vnf_isect", vnf, cp, offset, anchors]
2982    ) : !is_undef(region)? (
2983        assert(is_region(region),2)
2984        let( l = default(l, h) )
2985        two_d==true
2986          ? assert(is_undef(l))
2987            extent==true
2988              ? ["rgn_extent", region, cp, offset, anchors]
2989              : ["rgn_isect",  region, cp, offset, anchors]
2990          : assert(is_finite(l))
2991            let(
2992                shift = default(shift, [0,0]),
2993                scale = is_num(scale)? [scale,scale] : default(scale, [1,1]),
2994                twist = default(twist, 0)
2995            )
2996            assert(is_vector(shift,2))
2997            assert(is_vector(scale,2))
2998            assert(is_num(twist))
2999            extent==true
3000              ? ["extrusion_extent", region, l, twist, scale, shift, cp, offset, anchors]
3001              : ["extrusion_isect",  region, l, twist, scale, shift, cp, offset, anchors]
3002    ) :
3003    let(
3004        r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
3005    )
3006    !is_undef(r1)? (
3007        let( l = default(l, h) )
3008        !is_undef(l)? (
3009            let(
3010                shift = default(shift, [0,0]),
3011                r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
3012            )
3013            assert(is_num(r1) || is_vector(r1,2))
3014            assert(is_num(r2) || is_vector(r2,2))
3015            assert(is_num(l))
3016            assert(is_vector(shift,2))
3017            ["conoid", r1, r2, l, shift, axis, cp, offset, anchors]
3018        ) : (
3019            two_d? (
3020                assert(is_num(r1) || is_vector(r1,2))
3021                ["ellipse", r1, cp, offset, anchors]
3022            ) : (
3023                assert(is_num(r1) || is_vector(r1,3))
3024                ["spheroid", r1, cp, offset, anchors]
3025            )
3026        )
3027    ) :
3028    ["point", cp, offset, anchors];
3029
3030
3031
3032
3033
3034
3035//////////////////////////////////////////////////////////////////////////////////////////////////////////////
3036//
3037// Attachment internal functions
3038
3039
3040/// Internal Function: _attach_geom_2d()
3041/// Topics: Attachments
3042/// See Also: reorient(), attachable()
3043/// Usage:
3044///   bool = _attach_geom_2d(geom);
3045/// Description:
3046///   Returns true if the given attachment geometry description is for a 2D shape.
3047function _attach_geom_2d(geom) =
3048    let( type = geom[0] )
3049    type == "trapezoid" || type == "ellipse" ||
3050    type == "rgn_isect" || type == "rgn_extent";
3051
3052
3053/// Internal Function: _attach_geom_size()
3054/// Usage:
3055///   bounds = _attach_geom_size(geom);
3056/// Topics: Attachments
3057/// See Also: reorient(), attachable()
3058/// Description:
3059///   Returns the `[X,Y,Z]` bounding size for the given attachment geometry description.
3060function _attach_geom_size(geom) =
3061    let( type = geom[0] )
3062    type == "point"? [0,0,0] :
3063    type == "prismoid"? ( //size, size2, shift, axis
3064        let(
3065            size=geom[1], size2=geom[2], shift=point2d(geom[3]),
3066            maxx = max(size.x,size2.x),
3067            maxy = max(size.y,size2.y),
3068            z = size.z
3069        ) [maxx, maxy, z]
3070    ) : type == "conoid"? ( //r1, r2, l, shift
3071        let(
3072            r1=geom[1], r2=geom[2], l=geom[3],
3073            shift=point2d(geom[4]), axis=point3d(geom[5]),
3074            rx1 = default(r1[0],r1),
3075            ry1 = default(r1[1],r1),
3076            rx2 = default(r2[0],r2),
3077            ry2 = default(r2[1],r2),
3078            maxxr = max(rx1,rx2),
3079            maxyr = max(ry1,ry2)
3080        )
3081        approx(axis,UP)? [2*maxxr,2*maxyr,l] :
3082        approx(axis,RIGHT)? [l,2*maxyr,2*maxxr] :
3083        approx(axis,BACK)? [2*maxxr,l,2*maxyr] :
3084        [2*maxxr, 2*maxyr, l]
3085    ) : type == "spheroid"? ( //r
3086        let( r=geom[1] )
3087        is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r))
3088    ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
3089        let(
3090            vnf = geom[1]
3091        ) vnf==EMPTY_VNF? [0,0,0] :
3092        let(
3093            mm = pointlist_bounds(geom[1][0]),
3094            delt = mm[1]-mm[0]
3095        ) delt
3096    ) : type == "extrusion_isect" || type == "extrusion_extent"? ( //path, l
3097        let(
3098            mm = pointlist_bounds(flatten(geom[1])),
3099            delt = mm[1]-mm[0]
3100        ) [delt.x, delt.y, geom[2]]
3101    ) : type == "trapezoid"? ( //size, size2
3102        let(
3103            size=geom[1], size2=geom[2], shift=geom[3],
3104            maxx = max(size.x,size2+abs(shift))
3105        ) [maxx, size.y]
3106    ) : type == "ellipse"? ( //r
3107        let( r=geom[1] )
3108        is_num(r)? [2,2]*r : v_mul([2,2],point2d(r))
3109    ) : type == "rgn_isect" || type == "rgn_extent"? ( //path
3110        let(
3111            mm = pointlist_bounds(flatten(geom[1])),
3112            delt = mm[1]-mm[0]
3113        ) [delt.x, delt.y]
3114    ) :
3115    assert(false, "Unknown attachment geometry type.");
3116
3117
3118
3119/// Internal Function: _attach_geom_edge_path()
3120/// Usage:
3121///   angle = _attach_geom_edge_path(geom, edge);
3122/// Topics: Attachments
3123/// See Also: reorient(), attachable()
3124/// Description:
3125///   Returns the path and post-transform matrix of the indicated edge.
3126///   If the edge is invalid for the geometry, returns `undef`.
3127function _attach_geom_edge_path(geom, edge) =
3128    assert(is_vector(edge),str("Invalid edge: edge=",edge))
3129    let(
3130        type = geom[0],
3131        cp = _get_cp(geom),
3132        offset_raw = select(geom,-2),
3133        offset = [for (i=[0:2]) edge[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
3134        edge = point3d(edge)
3135    )
3136    type == "prismoid"? ( //size, size2, shift, axis
3137        let(all_comps_good = [for (c=edge) if (c!=sign(c)) 1]==[])
3138        assert(all_comps_good, "All components of an edge for a cuboid/prismoid must be -1, 0, or 1")
3139        let(edge_good = len([for (c=edge) if(c) 1])==2)
3140        assert(edge_good, "Invalid edge.")
3141        let(
3142            size = geom[1],
3143            size2 = geom[2],
3144            shift = point2d(geom[3]),
3145            axis = point3d(geom[4]),
3146            edge = rot(from=axis, to=UP, p=edge),
3147            offset = rot(from=axis, to=UP, p=offset),
3148            h = size.z,
3149            cpos = function(vec) let(
3150                        u = (vec.z + 1) / 2,
3151                        siz = lerp(point2d(size), size2, u) / 2,
3152                        z = vec.z * h / 2,
3153                        pos = point3d(v_mul(siz, point2d(vec)) + shift * u, z)
3154                    ) pos,
3155            ep1 = cpos([for (c=edge) c? c : -1]),
3156            ep2 = cpos([for (c=edge) c? c :  1]),
3157            cp = (ep1 + ep2) / 2,
3158            axy = point2d(edge),
3159            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
3160            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
3161            xang = atan2(h,(top-bot).x),
3162            yang = atan2(h,(top-bot).y),
3163            vecs = [
3164                if (edge.x) yrot(90-xang, p=sign(axy.x)*RIGHT),
3165                if (edge.y) xrot(yang-90, p=sign(axy.y)*BACK),
3166                if (edge.z) [0,0,sign(edge.z)]
3167            ], 
3168            segvec = cross(unit(vecs[1]), unit(vecs[0])),
3169            seglen = norm(ep2 - ep1),
3170            path = [
3171                cp - segvec * seglen/2,
3172                cp + segvec * seglen/2
3173            ],
3174            m = rot(from=UP,to=axis) * move(offset)
3175        ) [path, [vecs], m]
3176    ) : type == "conoid"? ( //r1, r2, l, shift, axis
3177        assert(edge.z && edge.z == sign(edge.z), "The Z component of an edge for a cylinder/cone must be -1 or 1")
3178        let(
3179            rr1 = geom[1],
3180            rr2 = geom[2],
3181            l = geom[3],
3182            shift = point2d(geom[4]),
3183            axis = point3d(geom[5]),
3184            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
3185            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
3186            edge = rot(from=axis, to=UP, p=edge),
3187            offset = rot(from=axis, to=UP, p=offset),
3188            maxr = max([each r1, each r2]),
3189            sides = segs(maxr),
3190            top = path3d(move(shift, p=ellipse(r=r2, $fn=sides)), l/2),
3191            bot = path3d(ellipse(r=r1, $fn=sides), -l/2),
3192            path = edge.z < 0 ? bot : top,
3193            path2 = edge.z < 0 ? top : bot,
3194            zed = edge.z<0? [0,0,-l/2] : point3d(shift,l/2),
3195            vecs = [
3196                for (i = idx(top)) let(
3197                    pt1 = (path[i] + select(path,i+1)) /2,
3198                    pt2 = (path2[i] + select(path2,i+1)) /2,
3199                    v1 = unit(zed - pt1),
3200                    v2 = unit(pt2 - pt1),
3201                    v3 = unit(cross(v1,v2)),
3202                    v4 = cross(v3,v2),
3203                    v5 = cross(v1,v3)
3204                ) [v4, v5]
3205            ],
3206            m = rot(from=UP,to=axis) * move(offset)
3207        ) edge.z>0
3208          ? [reverse(list_wrap(path)), reverse(vecs), m]
3209          : [list_wrap(path), vecs, m]
3210    ) : undef;
3211
3212
3213/// Internal Function: _attach_transform()
3214/// Usage: To Get a Transformation Matrix
3215///   mat = _attach_transform(anchor, spin, orient, geom);
3216/// Usage: To Transform Points, Paths, Patches, or VNFs
3217///   new_p = _attach_transform(anchor, spin, orient, geom, p);
3218/// Topics: Attachments
3219/// See Also: reorient(), attachable()
3220/// Description:
3221///   Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
3222///   the given geometry `geom` shape into position.
3223/// Arguments:
3224///   anchor = Anchor point to translate to the origin `[0,0,0]`.  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3225///   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3226///   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3227///   geom = The geometry description of the shape.
3228///   p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
3229function _attach_transform(anchor, spin, orient, geom, p) =
3230    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
3231    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
3232    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
3233    let(
3234        anchor = default(anchor, CENTER),
3235        spin   = default(spin,   0),
3236        orient = default(orient, UP),
3237        two_d = _attach_geom_2d(geom),
3238        m = ($attach_to != undef)? (
3239            let(
3240                anch = _find_anchor($attach_to, geom),
3241                pos = anch[1]
3242            )
3243            two_d?
3244                assert(is_num(spin))
3245                affine3d_zrot(spin) 
3246                   * rot(to=FWD, from=point3d(anch[2])) 
3247                   * affine3d_translate(point3d(-pos))
3248            :
3249                assert(is_num(spin) || is_vector(spin,3))
3250                let(
3251                    ang = vector_angle(anch[2], DOWN),
3252                    axis = vector_axis(anch[2], DOWN),
3253                    ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
3254                    axis2 = rot(p=axis,[0,0,ang2])
3255                )
3256                affine3d_rot_by_axis(axis2,ang)
3257                   * (is_num(spin)? affine3d_zrot(ang2+spin)
3258                                  : affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x) 
3259                                     * affine3d_zrot(ang2))
3260                   * affine3d_translate(point3d(-pos))
3261        ) : (
3262            let(
3263                pos = _find_anchor(anchor, geom)[1]
3264            )
3265            two_d? 
3266                assert(is_num(spin))
3267                affine3d_zrot(spin) * affine3d_translate(point3d(-pos))
3268            :
3269                assert(is_num(spin) || is_vector(spin,3))
3270                let(
3271                    axis = vector_axis(UP,orient),
3272                    ang = vector_angle(UP,orient)
3273                )
3274                affine3d_rot_by_axis(axis,ang) 
3275                    * ( is_num(spin)? affine3d_zrot(spin)  
3276                                    : affine3d_zrot(spin.z) * affine3d_yrot(spin.y) * affine3d_xrot(spin.x))
3277                    * affine3d_translate(point3d(-pos))
3278        )
3279    )
3280    is_undef(p)? m
3281  : is_vnf(p) && p==EMPTY_VNF? p 
3282  : apply(m, p);
3283
3284
3285function _get_cp(geom) =
3286    let(cp=select(geom,-3))
3287    is_vector(cp) ? cp
3288  : let(
3289        type = in_list(geom[0],["vnf_extent","vnf_isect"]) ? "vnf"
3290             : in_list(geom[0],["rgn_extent","rgn_isect"]) ? "path"
3291             : in_list(geom[0],["extrusion_extent","extrusion_isect"]) ? "xpath"
3292             : "other"
3293    )
3294    assert(type!="other", "Invalid cp value")
3295    cp=="centroid" ? (
3296       type=="vnf" && (len(geom[1][0])==0 || len(geom[1][1])==0) ? [0,0,0] :
3297       [each centroid(geom[1]), if (type=="xpath") 0]
3298    )
3299  : let(points = type=="vnf"?geom[1][0]:flatten(force_region(geom[1])))
3300    cp=="mean" ? [each mean(points), if (type=="xpath") 0]
3301  : cp=="box" ?[each  mean(pointlist_bounds(points)), if (type=="xpath") 0]
3302  : assert(false,"Invalid cp specification");
3303
3304
3305function _get_cp(geom) =
3306    let(cp=select(geom,-3))
3307    is_vector(cp) ? cp
3308  : let(
3309        is_vnf = in_list(geom[0],["vnf_extent","vnf_isect"])
3310    )
3311    cp == "centroid" ? (
3312       is_vnf && len(geom[1][1])==0
3313          ? [0,0,0]
3314          : centroid(geom[1])
3315    )
3316  : let(points = is_vnf?geom[1][0]:flatten(force_region(geom[1])))
3317    cp=="mean" ? mean(points)
3318  : cp=="box" ? mean(pointlist_bounds(points))
3319  : assert(false,"Invalid cp specification");
3320
3321
3322
3323function _force_anchor_2d(anchor) =
3324  assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D.  It must have either Y or Z component equal to zero.")
3325  anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
3326
3327
3328/// Internal Function: _find_anchor()
3329/// Usage:
3330///   anchorinfo = _find_anchor(anchor, geom);
3331/// Topics: Attachments
3332/// See Also: reorient(), attachable()
3333/// Description:
3334///   Calculates the anchor data for the given `anchor` vector or name, in the given attachment
3335///   geometry.  Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname
3336///   or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and
3337///   `ANG` is the angle to align with around the rotation axis of th anchor direction vector.
3338/// Arguments:
3339///   anchor = Vector or named anchor string.
3340///   geom = The geometry description of the shape.
3341function _find_anchor(anchor, geom) =
3342    is_string(anchor)? (
3343          anchor=="origin"? [anchor, CENTER, UP, 0]
3344        : let(
3345              anchors = last(geom),
3346              found = search([anchor], anchors, num_returns_per_match=1)[0]
3347          )
3348          assert(found!=[], str("Unknown anchor: ",anchor))
3349          anchors[found]
3350    ) :
3351    let(
3352        cp = _get_cp(geom),
3353        offset_raw = select(geom,-2),
3354        offset = [for (i=[0:2]) anchor[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
3355        type = geom[0]
3356    )
3357    assert(is_vector(anchor),str("Invalid anchor: anchor=",anchor))
3358    let(
3359        anchor = point3d(anchor),
3360        oang = (
3361            approx(point2d(anchor), [0,0])? 0 :
3362            atan2(anchor.y, anchor.x)+90
3363        )
3364    )
3365    type == "prismoid"? ( //size, size2, shift, axis
3366        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
3367        assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1")
3368        let(
3369            size=geom[1], size2=geom[2],
3370            shift=point2d(geom[3]), axis=point3d(geom[4]),
3371            override = geom[5](anchor)
3372        )
3373        let(
3374            size = [for (c = size) max(0,c)],
3375            size2 = [for (c = size2) max(0,c)],
3376            anch = rot(from=axis, to=UP, p=anchor),
3377            offset = rot(from=axis, to=UP, p=offset),
3378            h = size.z,
3379            u = (anch.z + 1) / 2,  // u is one of 0, 0.5, or 1
3380            axy = point2d(anch),
3381            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
3382            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
3383            pos = point3d(cp) + lerp(bot,top,u) + offset,
3384            vecs = anchor==CENTER? [UP]
3385              : [
3386                    if (anch.x!=0) unit(rot(from=UP, to=[(top-bot).x,0,max(0.01,h)], p=[axy.x,0,0]), UP),
3387                    if (anch.y!=0) unit(rot(from=UP, to=[0,(top-bot).y,max(0.01,h)], p=[0,axy.y,0]), UP),
3388                    if (anch.z!=0) unit([0,0,anch.z],UP)
3389                ],
3390            vec2 = anchor==CENTER? UP
3391              : len(vecs)==1? unit(vecs[0],UP)
3392              : len(vecs)==2? vector_bisect(vecs[0],vecs[1])
3393              : let(
3394                    v1 = vector_bisect(vecs[0],vecs[2]),
3395                    v2 = vector_bisect(vecs[1],vecs[2]),
3396                    p1 = plane_from_normal(yrot(90,p=v1)),
3397                    p2 = plane_from_normal(xrot(-90,p=v2)),
3398                    line = plane_intersection(p1,p2),
3399                    v3 = unit(line[1]-line[0],UP) * anch.z
3400                )
3401                unit(v3,UP),
3402            vec = default(override[1],rot(from=UP, to=axis, p=vec2)),
3403            pos2 = default(override[0],rot(from=UP, to=axis, p=pos))
3404        ) [anchor, pos2, vec, default(override[2],oang)]
3405    ) : type == "conoid"? ( //r1, r2, l, shift
3406        assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
3407        let(
3408            rr1=geom[1], rr2=geom[2], l=geom[3],
3409            shift=point2d(geom[4]), axis=point3d(geom[5]),
3410            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
3411            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
3412            anch = rot(from=axis, to=UP, p=anchor),
3413            offset = rot(from=axis, to=UP, p=offset),
3414            u = (anch.z+1)/2,
3415            axy = unit(point2d(anch),[0,0]),
3416            bot = point3d(v_mul(r1,axy), -l/2),
3417            top = point3d(v_mul(r2,axy)+shift, l/2),
3418            pos = point3d(cp) + lerp(bot,top,u) + offset,
3419            sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)),
3420            vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
3421            vec = anch==CENTER? CENTER :
3422                approx(axy,[0,0])? unit(anch,UP) :
3423                approx(anch.z,0)? sidevec :
3424                unit((sidevec+vvec)/2,UP),
3425            pos2 = rot(from=UP, to=axis, p=pos),
3426            vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec)
3427        ) [anchor, pos2, vec2, oang]
3428    ) : type == "point"? (
3429        let(
3430            anchor = unit(point3d(anchor),CENTER),
3431            pos = point3d(cp) + point3d(offset),
3432            vec = unit(anchor,UP)
3433        ) [anchor, pos, vec, oang]
3434    ) : type == "spheroid"? ( //r
3435        let(
3436            rr = geom[1],
3437            r = is_num(rr)? [rr,rr,rr] : point3d(rr),
3438            anchor = unit(point3d(anchor),CENTER),
3439            pos = point3d(cp) + v_mul(r,anchor) + point3d(offset),
3440            vec = unit(v_mul(r,anchor),UP)
3441        ) [anchor, pos, vec, oang]
3442    ) : type == "vnf_isect"? ( //vnf
3443        let( vnf=geom[1] )
3444        approx(anchor,CTR)? [anchor, cp, UP, 0] :      // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3445        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
3446        let(
3447            eps = 1/2048,
3448            points = vnf[0],
3449            faces = vnf[1],
3450            rpts = apply(rot(from=anchor, to=RIGHT) * move(-cp), points),
3451            hits = [
3452                for (face = faces)
3453                    let(
3454                        verts = select(rpts, face),
3455                        ys = column(verts,1),
3456                        zs = column(verts,2)
3457                    )
3458                    if (max(ys) >= -eps && max(zs) >= -eps &&
3459                        min(ys) <=  eps &&  min(zs) <=  eps)
3460                        let(
3461                            poly = select(points, face),
3462                            isect = polygon_line_intersection(poly, [cp,cp+anchor], eps=eps),
3463                            ptlist = is_undef(isect) ? [] :
3464                                     is_vector(isect) ? [isect]
3465                                                      : flatten(isect),   // parallel to a face
3466                            n = len(ptlist)>0 ? polygon_normal(poly) : undef
3467                        )
3468                        for(pt=ptlist) [anchor * (pt-cp), n, pt]
3469            ]
3470        )
3471        assert(len(hits)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
3472        let(
3473            furthest = max_index(column(hits,0)),
3474            dist = hits[furthest][0],
3475            pos = hits[furthest][2],
3476            hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
3477            unorms = [
3478                      for (i = idx(hitnorms))
3479                          let(
3480                              thisnorm = hitnorms[i],
3481                              isdup = [
3482                                       for (j = [i+1:1:len(hitnorms)-1])
3483                                           if (approx(thisnorm, hitnorms[j])) 1
3484                                      ] != []
3485                          )
3486                          if (!isdup) thisnorm
3487                     ],
3488            n = unit(sum(unorms)),
3489            oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
3490        )
3491        [anchor, pos, n, oang]
3492    ) : type == "vnf_extent"? ( //vnf
3493        let( vnf=geom[1] )
3494        approx(anchor,CTR)? [anchor, cp, UP, 0] :      // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3495        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
3496        let(
3497            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
3498            maxx = max(column(rpts,0)),
3499            idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
3500            avep = sum(select(rpts,idxs))/len(idxs),
3501            mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
3502            pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
3503        ) [anchor, pos, anchor, oang]
3504    ) : type == "trapezoid"? ( //size, size2, shift, override
3505        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
3506        assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
3507        let(
3508            anchor=_force_anchor_2d(anchor),
3509            size=geom[1], size2=geom[2], shift=geom[3],
3510            u = (anchor.y+1)/2,  // 0<=u<=1
3511            frpt = [size.x/2*anchor.x, -size.y/2],
3512            bkpt = [size2/2*anchor.x+shift, size.y/2],
3513            override = geom[4](anchor),
3514            pos = override[0] != undef? override[0] :
3515                point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset),
3516            svec = approx(bkpt,frpt)? [anchor.x,0,0] :
3517                point3d(line_normal(bkpt,frpt)*anchor.x),
3518            vec = is_def(override[1]) ? override[1]
3519                : anchor.y == 0? ( anchor.x == 0? BACK : svec )
3520                : anchor.x == 0? [0,anchor.y,0]
3521                : unit((svec + [0,anchor.y,0]) / 2, [0,anchor.y,0])
3522        ) [anchor, pos, vec, 0]
3523    ) : type == "ellipse"? ( //r
3524        let(
3525            anchor = unit(_force_anchor_2d(anchor),[0,0]),
3526            r = force_list(geom[1],2),
3527            pos = approx(anchor.x,0)
3528                ? [0,sign(anchor.y)*r.y]
3529                : let(
3530                       m = anchor.y/anchor.x,
3531                       px = approx(min(r),0)? 0 :
3532                           sign(anchor.x) * sqrt(1/(1/sqr(r.x) + m*m/sqr(r.y)))
3533                  )
3534                  [px,m*px],
3535            vec = approx(min(r),0)? (approx(norm(anchor),0)? BACK : anchor) :
3536                unit([r.y/r.x*pos.x, r.x/r.y*pos.y],BACK)
3537        ) [anchor, point2d(cp+offset)+pos, vec, 0]
3538    ) : type == "rgn_isect"? ( //region
3539        let(
3540            anchor = _force_anchor_2d(anchor),
3541            rgn = force_region(move(-point2d(cp), p=geom[1]))
3542        )
3543        approx(anchor,[0,0])? [anchor, cp, BACK, 0] :     // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3544        let(
3545            isects = [
3546                for (path=rgn, t=triplet(path,true)) let(
3547                    seg1 = [t[0],t[1]],
3548                    seg2 = [t[1],t[2]],
3549                    isect = line_intersection([[0,0],anchor], seg1, RAY, SEGMENT),
3550                    n = is_undef(isect)? [0,1] :
3551                        !approx(isect, t[1])? line_normal(seg1) :
3552                        unit((line_normal(seg1)+line_normal(seg2))/2,[0,1]),
3553                    n2 = vector_angle(anchor,n)>90? -n : n
3554                )
3555                if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2]
3556            ]
3557        )
3558        assert(len(isects)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
3559        let(
3560            maxidx = max_index(column(isects,0)),
3561            isect = isects[maxidx],
3562            pos = point2d(cp) + isect[1],
3563            vec = unit(isect[2],[0,1])
3564        ) [anchor, pos, vec, 0]
3565    ) : type == "rgn_extent"? ( //region
3566        let( anchor = _force_anchor_2d(anchor) )
3567        approx(anchor,[0,0])? [anchor, cp, BACK, 0] :   // CENTER anchors anchor on cp, "origin" anchors on [0,0]
3568        let(
3569            rgn = force_region(geom[1]),
3570            rpts = rot(from=anchor, to=RIGHT, p=flatten(rgn)),
3571            maxx = max(column(rpts,0)),
3572            ys = [for (pt=rpts) if (approx(pt.x, maxx)) pt.y],
3573            midy = (min(ys)+max(ys))/2,
3574            pos = rot(from=RIGHT, to=anchor, p=[maxx,midy])
3575        ) [anchor, pos, unit(anchor,BACK), 0]
3576    ) : type=="extrusion_extent" || type=="extrusion_isect" ? (  // extruded region
3577        assert(in_list(anchor.z,[-1,0,1]), "The Z component of an anchor for an extruded 2D shape must be -1, 0, or 1.")
3578        let(
3579            anchor_xy = point2d(anchor),
3580            rgn = geom[1],
3581            L = geom[2],
3582            twist = geom[3],
3583            scale = geom[4],
3584            shift = geom[5],
3585            u = (anchor.z + 1) / 2,
3586            shmat = move(lerp([0,0], shift, u)),
3587            scmat = scale(lerp([1,1], scale, u)),
3588            twmat = zrot(lerp(0, -twist, u)),
3589            mat = shmat * scmat * twmat
3590        )
3591        approx(anchor_xy,[0,0]) ? [anchor, apply(mat, point3d(cp,anchor.z*L/2)), unit(anchor, UP), oang] :
3592        let(
3593            newrgn = apply(mat, rgn),
3594            newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="extrusion_extent", cp=cp),
3595            topmat = anchor.z!=0 ? []
3596                   : move(shift)*scale(scale)*zrot(-twist),
3597            topgeom = anchor.z!=0? []
3598                    : attach_geom(two_d=true, region=apply(topmat,rgn), extent=type=="extrusion_extent", cp=cp),
3599            top2d =  anchor.z!=0? []
3600                  : _find_anchor(anchor_xy, topgeom),
3601            result2d = _find_anchor(anchor_xy, newgeom),
3602            pos = point3d(result2d[1], anchor.z*L/2),
3603            vec = anchor.z==0? rot(from=UP,to=point3d(top2d[1],L/2)-point3d(result2d[1]),p=point3d(result2d[2]))
3604                : unit(point3d(result2d[2], anchor.z),UP),
3605            oang = atan2(vec.y,vec.x) + 90
3606        )
3607        [anchor, pos, vec, oang]
3608    ) :
3609    assert(false, "Unknown attachment geometry type.");
3610
3611
3612/// Internal Function: _is_shown()
3613/// Usage:
3614///   bool = _is_shown();
3615/// Topics: Attachments
3616/// See Also: reorient(), attachable()
3617/// Description:
3618///   Returns true if objects should currently be shown based on the tag settings.
3619function _is_shown() =
3620    assert(is_list($tags_shown) || $tags_shown=="ALL")
3621    assert(is_list($tags_hidden))
3622    let(
3623        dummy=is_undef($tags) ? 0 : echo("Use tag() instead of $tags for specifying an object's tag."),
3624        $tag = default($tag,$tags)
3625    )
3626    assert(is_string($tag), str("Tag value (",$tag,") is not a string"))
3627    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"))
3628    let(
3629        shown  = $tags_shown=="ALL" || in_list($tag,$tags_shown),
3630        hidden = in_list($tag, $tags_hidden)
3631    )
3632    shown && !hidden;
3633
3634
3635// Section: Visualizing Anchors
3636
3637/// Internal Function: _standard_anchors()
3638/// Usage:
3639///   anchs = _standard_anchors([two_d]);
3640/// Description:
3641///   Return the vectors for all standard anchors.
3642/// Arguments:
3643///   two_d = If true, returns only the anchors where the Z component is 0.  Default: false
3644function _standard_anchors(two_d=false) = [
3645    for (
3646        zv = [
3647            if (!two_d) TOP,
3648            CENTER,
3649            if (!two_d) BOTTOM
3650        ],
3651        yv = [FRONT, CENTER, BACK],
3652        xv = [LEFT, CENTER, RIGHT]
3653    ) xv+yv+zv
3654];
3655
3656
3657
3658// Module: show_anchors()
3659// Synopsis: Shows anchors for the parent object.
3660// SynTags: Geom
3661// Topics: Attachments
3662// See Also: expose_anchors(), anchor_arrow(), anchor_arrow2d(), frame_ref()
3663// Usage:
3664//   PARENT() show_anchors([s], [std=], [custom=]);
3665// Description:
3666//   Show all standard anchors for the parent object.
3667// Arguments:
3668//   s = Length of anchor arrows.
3669//   ---
3670//   std = If true show standard anchors.  Default: true
3671//   custom = If true show named anchors.  Default: true
3672// Example(FlatSpin,VPD=333):
3673//   cube(50, center=true) show_anchors();
3674module show_anchors(s=10, std=true, custom=true) {
3675    check = assert($parent_geom != undef);
3676    two_d = _attach_geom_2d($parent_geom);
3677    if (std) {
3678        for (anchor=_standard_anchors(two_d=two_d)) {
3679            if(two_d) {
3680                attach(anchor) anchor_arrow2d(s);
3681            } else {
3682                attach(anchor) anchor_arrow(s);
3683            }
3684        }
3685    }
3686    if (custom) {
3687        for (anchor=last($parent_geom)) {
3688            attach(anchor[0]) {
3689                if(two_d) {
3690                    anchor_arrow2d(s, color="cyan");
3691                } else {
3692                    anchor_arrow(s, color="cyan");
3693                }
3694                color("black")
3695                tag("anchor-arrow") {
3696                    xrot(two_d? 0 : 90) {
3697                        back(s/3) {
3698                            yrot_copies(n=2)
3699                            up(two_d? 0.51 : s/30) {
3700                                linear_extrude(height=0.01, convexity=12, center=true) {
3701                                    text(text=anchor[0], size=s/4, halign="center", valign="center", font="Helvetica", $fn=36);
3702                                }
3703                            }
3704                        }
3705                    }
3706                }
3707                color([1, 1, 1, 1])
3708                tag("anchor-arrow") {
3709                    xrot(two_d? 0 : 90) {
3710                        back(s/3) {
3711                             cube([s/4.5*len(anchor[0]), s/3, 0.01], center=true);
3712                        }
3713                   }
3714                }
3715            }
3716        }
3717    }
3718    children();
3719}
3720
3721
3722// Module: anchor_arrow()
3723// Synopsis: Shows a 3d anchor orientation arrow.
3724// SynTags: Geom
3725// Topics: Attachments
3726// See Also: anchor_arrow2d(), show_anchors(), expose_anchors(), frame_ref(), generic_airplane()
3727// Usage:
3728//   anchor_arrow([s], [color], [flag], [anchor=], [orient=], [spin=]) [ATTACHMENTS];
3729// Description:
3730//   Show an anchor orientation arrow.  By default, tagged with the name "anchor-arrow".
3731// Arguments:
3732//   s = Length of the arrows.  Default: `10`
3733//   color = Color of the arrow.  Default: `[0.333, 0.333, 1]`
3734//   flag = If true, draw the orientation flag on the arrowhead.  Default: true
3735//   ---
3736//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3737//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3738//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3739// Example:
3740//   anchor_arrow(s=20);
3741module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tag="anchor-arrow", $fn=12, anchor=BOT, spin=0, orient=UP) {
3742    attachable(anchor,spin,orient, r=s/6, l=s) {
3743        down(s/2)
3744        recolor("gray") spheroid(d=s/6) {
3745            attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
3746                attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
3747                    if(flag) {
3748                        position(BOT)
3749                            recolor([1,0.5,0.5])
3750                                cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
3751                    }
3752                }
3753            }
3754        }
3755        children();
3756    }
3757}
3758
3759
3760
3761// Module: anchor_arrow2d()
3762// Synopsis: Shows a 2d anchor orientation arrow.
3763// SynTags: Geom
3764// Topics: Attachments
3765// See Also: anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3766// Usage:
3767//   anchor_arrow2d([s], [color]);
3768// Description:
3769//   Show an anchor orientation arrow.
3770// Arguments:
3771//   s = Length of the arrows.
3772//   color = Color of the arrow.
3773// Example:
3774//   anchor_arrow2d(s=20);
3775module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tag="anchor-arrow") {
3776    color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
3777}
3778
3779
3780
3781// Module: expose_anchors()
3782// Synopsis: Used to show a transparent object with solid color anchor arrows.
3783// Topics: Attachments
3784// See Also: anchor_arrow2d(), show_anchors(), show_anchors(), frame_ref()
3785// Usage:
3786//   expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}
3787// Description:
3788//   Used in combination with show_anchors() to display an object in transparent gray with its anchors in solid color.
3789//   Children will appear transparent and any anchor arrows drawn with will appear in solid color.
3790// Arguments:
3791//   opacity = The opacity of the children.  0.0 is invisible, 1.0 is opaque.  Default: 0.2
3792// Example(FlatSpin,VPD=333):
3793//   expose_anchors() cube(50, center=true) show_anchors();
3794module expose_anchors(opacity=0.2) {
3795    show_only("anchor-arrow")
3796        children();
3797    hide("anchor-arrow")
3798        color(is_undef($color) || $color=="default" ? [0,0,0] :
3799              is_string($color) ? $color
3800                                : point3d($color),
3801              opacity)
3802            children();
3803}
3804
3805
3806
3807// Module: show_transform_list()
3808// Synopsis: Shows a list of transforms and how they connect.
3809// SynTags: Geom
3810// Topics: Attachments
3811// See Also: generic_airplane(), anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3812// Usage:
3813//   show_transform_list(tlist, [s]);
3814//   show_transform_list(tlist) {CHILDREN};
3815// Description:
3816//   Given a list of transformation matrices, shows the position and orientation of each one.
3817//   A line is drawn from each transform position to the next one, and an orientation indicator is
3818//   shown at each position.  If a child is passed, that child will be used as the orientation indicator.
3819//   By default, a {{generic_airplane()}} is used as the orientation indicator.
3820// Arguments:
3821//   s = Length of the {{generic_airplane()}}.  Default: 5
3822// Example:
3823//   tlist = [
3824//       zrot(90),
3825//       zrot(90) * fwd(30) * zrot(30),
3826//       zrot(90) * fwd(30) * zrot(30) *
3827//           fwd(35) * xrot(-30),
3828//       zrot(90) * fwd(30) * zrot(30) *
3829//           fwd(35) * xrot(-30) * fwd(40) * yrot(15),
3830//   ];
3831//   show_transform_list(tlist, s=20);
3832// Example:
3833//   tlist = [
3834//       zrot(90),
3835//       zrot(90) * fwd(30) * zrot(30),
3836//       zrot(90) * fwd(30) * zrot(30) *
3837//           fwd(35) * xrot(-30),
3838//       zrot(90) * fwd(30) * zrot(30) *
3839//           fwd(35) * xrot(-30) * fwd(40) * yrot(15),
3840//   ];
3841//   show_transform_list(tlist) frame_ref();
3842module show_transform_list(tlist, s=5) {
3843    path = [for (m = tlist) apply(m, [0,0,0])];
3844    stroke(path, width=s*0.03);
3845    for (m = tlist) {
3846        multmatrix(m) {
3847            if ($children>0) children();
3848            else generic_airplane(s=s);
3849        }
3850    }
3851}
3852
3853
3854// Module: generic_airplane()
3855// Synopsis: Shows a generic airplane shape, useful for viewing orientations.
3856// SynTags: Geom
3857// Topics: Attachments
3858// See Also: anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3859// Usage:
3860//   generic_airplane([s]);
3861// Description:
3862//   Creates a generic airplane shape.  This can be useful for viewing the orientation of 3D transforms.
3863// Arguments:
3864//   s = Length of the airplane.  Default: 5
3865// Example:
3866//   generic_airplane(s=20);
3867module generic_airplane(s=5) {
3868    $fn = max(segs(0.05*s), 12);
3869    color("#ddd")
3870    fwd(s*0.05)
3871    ycyl(l=0.7*s, d=0.1*s) {
3872        attach(FWD) top_half(s=s) zscale(2) sphere(d=0.1*s);
3873        attach(BACK,FWD) ycyl(l=0.2*s, d1=0.1*s, d2=0.05*s) {
3874            yrot_copies([-90,0,90])
3875                prismoid(s*[0.01,0.2], s*[0.01,0.05],
3876                    h=0.2*s, shift=s*[0,0.15], anchor=BOT);
3877        }
3878        yrot_copies([-90,90])
3879            prismoid(s*[0.01,0.2], s*[0.01,0.05],
3880                h=0.5*s, shift=s*[0,0.15], anchor=BOT);
3881    }
3882    color("#777") zcopies(0.1*s) sphere(d=0.02*s);
3883    back(0.09*s) {
3884        color("#f00") right(0.46*s) sphere(d=0.04*s);
3885        color("#0f0") left(0.46*s) sphere(d=0.04*s);
3886    }
3887}
3888
3889
3890
3891// Module: frame_ref()
3892// Synopsis: Shows axis orientation arrows.
3893// SynTags: Geom
3894// Topics: Attachments
3895// See Also: anchor_arrow(), anchor_arrow2d(), show_anchors(), expose_anchors()
3896// Usage:
3897//   frame_ref(s, opacity);
3898// Description:
3899//   Displays X,Y,Z axis arrows in red, green, and blue respectively.
3900// Arguments:
3901//   s = Length of the arrows.
3902//   opacity = The opacity of the arrows.  0.0 is invisible, 1.0 is opaque.  Default: 1.0
3903// Examples:
3904//   frame_ref(25);
3905//   frame_ref(30, opacity=0.5);
3906module frame_ref(s=15, opacity=1) {
3907    cube(0.01, center=true) {
3908        attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
3909        attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
3910        attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
3911        children();
3912    }
3913}
3914
3915
3916////////////////////////////////////////////////////////////////////////////////////////////////////
3917////////////////////////////////////////////////////////////////////////////////////////////////////
3918////////////////////////////////////////////////////////////////////////////////////////////////////
3919////////////////////////////////////////////////////////////////////////////////////////////////////
3920///
3921/// Code after this is internal code for managing edge and corner sets and for displaying
3922/// edge and corners in the docs
3923///
3924
3925module _edges_text3d(txt,size=3) {
3926    if (is_list(txt)) {
3927        for (i=idx(txt)) {
3928            down((i-len(txt)/2+0.5)*size*1.5) {
3929                _edges_text3d(txt[i], size=size);
3930            }
3931        }
3932    } else {
3933        xrot(90) color("#000")
3934        linear_extrude(height=0.1) {
3935            text(text=txt, size=size, halign="center", valign="center");
3936        }
3937    }
3938}
3939
3940
3941function _edges_vec_txt(x) = is_string(x)? str("\"", x, "\"") :
3942    assert(is_string(x) || is_vector(x,3), str(x))
3943    let(
3944        lst = concat(
3945            x.z>0? ["TOP"]   : x.z<0? ["BOT"]  : [],
3946            x.y>0? ["BACK"]  : x.y<0? ["FWD"]  : [],
3947            x.x>0? ["RIGHT"] : x.x<0? ["LEFT"] : []
3948        ),
3949        out = [
3950           for (i = idx(lst))
3951           i>0? str("+",lst[i]) : lst[i]
3952        ]
3953    ) out;
3954
3955
3956function _edges_text(edges) =
3957    is_string(edges) ? [str("\"",edges,"\"")] :
3958    edges==EDGES_NONE ? ["EDGES_NONE"] :
3959    edges==EDGES_ALL ? ["EDGES_ALL"] :
3960    _is_edge_array(edges) ? [""] :
3961    is_vector(edges,3) ? _edges_vec_txt(edges) :
3962    is_list(edges) ? let(
3963        lst = [for (x=edges) each _edges_text(x)],
3964        out = [
3965            for (i=idx(lst))
3966            str(
3967                (i==0? "[" : ""),
3968                lst[i],
3969                (i<len(lst)-1? "," : ""),
3970                (i==len(lst)-1? "]" : "")
3971            )
3972        ]
3973    ) out :
3974    [""];
3975
3976
3977
3978/// Internal Constant: EDGES_NONE
3979/// Topics: Edges
3980/// See Also: EDGES_ALL, edges()
3981/// Description:
3982///   The set of no edges.
3983/// Figure(3D):
3984///   _show_edges(edges="NONE");
3985EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]];
3986
3987
3988/// Internal Constant: EDGES_ALL
3989/// Topics: Edges
3990/// See Also: EDGES_NONE, edges()
3991/// Description:
3992///   The set of all edges.
3993/// Figure(3D):
3994///   _show_edges(edges="ALL");
3995EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]];
3996
3997
3998/// Internal Constant: EDGES_OFFSETS
3999/// Topics: Edges
4000/// See Also: EDGES_NONE, EDGES_ALL, edges()
4001/// Description:
4002///   The vectors pointing to the center of each edge of a unit sized cube.
4003///   Each item in an edge array will have a corresponding vector in this array.
4004EDGE_OFFSETS = [
4005    [
4006        [ 0,-1,-1],
4007        [ 0, 1,-1],
4008        [ 0,-1, 1],
4009        [ 0, 1, 1]
4010    ], [
4011        [-1, 0,-1],
4012        [ 1, 0,-1],
4013        [-1, 0, 1],
4014        [ 1, 0, 1]
4015    ], [
4016        [-1,-1, 0],
4017        [ 1,-1, 0],
4018        [-1, 1, 0],
4019        [ 1, 1, 0]
4020    ]
4021];
4022
4023
4024
4025/// Internal Function: _is_edge_array()
4026/// Topics: Edges, Type Checking
4027/// Usage:
4028///   bool = _is_edge_array(x);
4029/// Description:
4030///   Returns true if the given value has the form of an edge array.
4031/// Arguments:
4032///   x = The item to check the type of.
4033/// See Also: edges(), EDGES_NONE, EDGES_ALL
4034function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
4035
4036
4037function _edge_set(v) =
4038    _is_edge_array(v)? v : [
4039    for (ax=[0:2]) [
4040        for (b=[-1,1], a=[-1,1]) let(
4041            v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
4042        ) (
4043            is_string(v)? (
4044                v=="X"? (ax==0) :   // Return all X axis aligned edges.
4045                v=="Y"? (ax==1) :   // Return all Y axis aligned edges.
4046                v=="Z"? (ax==2) :   // Return all Z axis aligned edges.
4047                v=="ALL"? true :    // Return all edges.
4048                v=="NONE"? false :  // Return no edges.
4049                let(valid_values = ["X", "Y", "Z", "ALL", "NONE"])
4050                assert(
4051                    in_list(v, valid_values),
4052                    str(v, " must be a vector, edge array, or one of ", valid_values)
4053                ) v
4054            ) :
4055            let(nonz = sum(v_abs(v)))
4056            nonz==2? (v==v2) :  // Edge: return matching edge.
4057            let(
4058                matches = num_true([
4059                    for (i=[0:2]) v[i] && (v[i]==v2[i])
4060                ])
4061            )
4062            nonz==1? (matches==1) :  // Face: return surrounding edges.
4063            (matches==2)             // Corner: return touching edges.
4064        )? 1 : 0
4065    ]
4066];
4067
4068
4069/// Internal Function: _normalize_edges()
4070/// Topics: Edges
4071/// Usage:
4072///   edges = _normalize_edges(v);
4073/// Description:
4074///   Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
4075///   or `0`, if it was originally less than or equal to `0`.
4076/// See Also:  edges(), EDGES_NONE, EDGES_ALL
4077function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
4078
4079
4080
4081
4082/// Internal Function: _edges()
4083/// Topics: Edges
4084/// Usage:
4085///   edgs = _edges(v);
4086///   edgs = _edges(v, except);
4087///
4088/// Description:
4089///   Takes a list of edge set descriptors, and returns a normalized edges array
4090///   that represents all those given edges.
4091/// Arguments:
4092///   v = The edge set to include.
4093///   except = The edge set to specifically exclude, even if they are in `v`.
4094///
4095/// See Also:  EDGES_NONE, EDGES_ALL
4096///
4097function _edges(v, except=[]) =
4098    v==[] ? EDGES_NONE :
4099    (is_string(v) || is_vector(v) || _is_edge_array(v))? _edges([v], except=except) :
4100    (is_string(except) || is_vector(except) || _is_edge_array(except))? _edges(v, except=[except]) :
4101    except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
4102    _normalize_edges(
4103        _normalize_edges(sum([for (x=v) _edge_set(x)])) -
4104        sum([for (x=except) _edge_set(x)])
4105    );
4106
4107
4108/// Internal Module: _show_edges()
4109/// Topics: Edges, Debugging
4110/// Usage:
4111///   _show_edges(edges, [size=], [text=], [txtsize=]);
4112/// Description:
4113///   Draws a semi-transparent cube with the given edges highlighted in red.
4114/// Arguments:
4115///   edges = The edges to highlight.
4116///   size = The scalar size of the cube.
4117///   text = The text to show on the front of the cube.
4118///   txtsize = The size of the text.
4119/// See Also: _edges(), EDGES_NONE, EDGES_ALL
4120/// Example:
4121///   _show_edges(size=30, edges=["X","Y"]);
4122module _show_edges(edges="ALL", size=20, text, txtsize=3,toplabel) {
4123    edge_set = _edges(edges);
4124    text = !is_undef(text) ? text : _edges_text(edges);
4125    color("red") {
4126        for (axis=[0:2], i=[0:3]) {
4127            if (edge_set[axis][i] > 0) {
4128                translate(EDGE_OFFSETS[axis][i]*size/2) {
4129                    if (axis==0) xcyl(h=size, d=2);
4130                    if (axis==1) ycyl(h=size, d=2);
4131                    if (axis==2) zcyl(h=size, d=2);
4132                }
4133            }
4134        }
4135    }
4136    fwd(size/2) _edges_text3d(text, size=txtsize);
4137    color("yellow",0.7) cuboid(size=size);
4138    vpr = [55,0,25];
4139    color("black")
4140    if (is_def(toplabel))
4141      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=0.1,orient=UP,anchor=FRONT);
4142}
4143
4144
4145
4146
4147/// Internal Constant: CORNERS_NONE
4148/// Topics: Corners
4149/// Description:
4150///   The set of no corners.
4151/// Figure(3D):
4152///   _show_corners(corners="NONE");
4153/// See Also: CORNERS_ALL, corners()
4154CORNERS_NONE = [0,0,0,0,0,0,0,0];  // No corners.
4155
4156
4157/// Internal Constant: CORNERS_ALL
4158/// Topics: Corners
4159/// Description:
4160///   The set of all corners.
4161/// Figure(3D):
4162///   _show_corners(corners="ALL");
4163/// See Also: CORNERS_NONE, _corners()
4164CORNERS_ALL = [1,1,1,1,1,1,1,1];
4165
4166
4167/// Internal Constant: CORNER_OFFSETS
4168/// Topics: Corners
4169/// Description:
4170///   The vectors pointing to each corner of a unit sized cube.
4171///   Each item in a corner array will have a corresponding vector in this array.
4172/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4173CORNER_OFFSETS = [
4174    [-1,-1,-1], [ 1,-1,-1], [-1, 1,-1], [ 1, 1,-1],
4175    [-1,-1, 1], [ 1,-1, 1], [-1, 1, 1], [ 1, 1, 1]
4176];
4177
4178
4179
4180
4181/// Internal Function: _is_corner_array()
4182/// Topics: Corners, Type Checking
4183/// Usage:
4184///   bool = _is_corner_array(x)
4185/// Description:
4186///   Returns true if the given value has the form of a corner array.
4187/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4188function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
4189
4190
4191/// Internal Function: _normalize_corners()
4192/// Topics: Corners
4193/// Usage:
4194///   corns = _normalize_corners(v);
4195/// Description:
4196///   Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
4197///   or `0`, if it was originally less than or equal to `0`.
4198/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4199function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
4200
4201
4202function _corner_set(v) =
4203    _is_corner_array(v)? v : [
4204    for (i=[0:7]) let(
4205        v2 = CORNER_OFFSETS[i]
4206    ) (
4207        is_string(v)? (
4208            v=="ALL"? true :    // Return all corners.
4209            v=="NONE"? false :  // Return no corners.
4210            let(valid_values = ["ALL", "NONE"])
4211            assert(
4212                in_list(v, valid_values),
4213                str(v, " must be a vector, corner array, or one of ", valid_values)
4214            ) v
4215        ) :
4216        all([for (i=[0:2]) !v[i] || (v[i]==v2[i])])
4217    )? 1 : 0
4218];
4219
4220
4221/// Function: _corners()
4222/// Topics: Corners
4223/// Usage:
4224///   corns = _corners(v);
4225///   corns = _corners(v, except);
4226/// Description:
4227///   Takes a list of corner set descriptors, and returns a normalized corners array
4228///   that represents all those given corners.  If the `except` argument is given
4229///   a list of corner set descriptors, then all those corners will be removed
4230///   from the returned corners array.  If either argument only has a single corner
4231///   set descriptor, you do not have to pass it in a list.
4232function _corners(v, except=[]) =
4233    v==[] ? CORNERS_NONE :
4234    (is_string(v) || is_vector(v) || _is_corner_array(v))? _corners([v], except=except) :
4235    (is_string(except) || is_vector(except) || _is_corner_array(except))? _corners(v, except=[except]) :
4236    except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
4237    let(
4238        a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
4239        b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
4240    ) _normalize_corners(a - b);
4241
4242
4243/// Internal Function: _corner_edges()
4244/// Topics: Corners
4245/// Description:
4246///   Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
4247///   axis that are in the edge set and touch the given corner.
4248/// Arguments:
4249///   edges = Standard edges array.
4250///   v = Vector pointing to the corner to count edge intersections at.
4251/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4252function _corner_edges(edges, v) =
4253    let(u = (v+[1,1,1])/2) [edges[0][u.y+u.z*2], edges[1][u.x+u.z*2], edges[2][u.x+u.y*2]];
4254
4255
4256/// InternalFunction: _corner_edge_count()
4257/// Topics: Corners
4258/// Description:
4259///   Counts how many given edges intersect at a specific corner.
4260/// Arguments:
4261///   edges = Standard edges array.
4262///   v = Vector pointing to the corner to count edge intersections at.
4263/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4264function _corner_edge_count(edges, v) =
4265    let(u = (v+[1,1,1])/2) edges[0][u.y+u.z*2] + edges[1][u.x+u.z*2] + edges[2][u.x+u.y*2];
4266
4267
4268function _corners_text(corners) =
4269    is_string(corners) ? [str("\"",corners,"\"")] :
4270    corners==CORNERS_NONE ? ["CORNERS_NONE"] :
4271    corners==CORNERS_ALL ? ["CORNERS_ALL"] :
4272    _is_corner_array(corners) ? [""] :
4273    is_vector(corners,3) ? _edges_vec_txt(corners) :
4274    is_list(corners) ? let(
4275        lst = [for (x=corners) each _corners_text(x)],
4276        out = [
4277            for (i=idx(lst))
4278            str(
4279                (i==0? "[" : ""),
4280                lst[i],
4281                (i<len(lst)-1? "," : ""),
4282                (i==len(lst)-1? "]" : "")
4283            )
4284        ]
4285    ) out :
4286    [""];
4287
4288
4289/// Internal Module: _show_corners()
4290/// Topics: Corners, Debugging
4291/// Usage:
4292///   _show_corners(corners, [size=], [text=], [txtsize=]);
4293/// Description:
4294///   Draws a semi-transparent cube with the given corners highlighted in red.
4295/// Arguments:
4296///   corners = The corners to highlight.
4297///   size = The scalar size of the cube.
4298///   text = If given, overrides the text to be shown on the front of the cube.
4299///   txtsize = The size of the text.
4300/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
4301/// Example:
4302///   _show_corners(corners=FWD+RIGHT, size=30);
4303module _show_corners(corners="ALL", size=20, text, txtsize=3,toplabel) {
4304    corner_set = _corners(corners);
4305    text = !is_undef(text) ? text : _corners_text(corners);
4306    for (i=[0:7]) if (corner_set[i]>0)
4307        translate(CORNER_OFFSETS[i]*size/2)
4308            color("red") sphere(d=2, $fn=16);
4309    fwd(size/2) _edges_text3d(text, size=txtsize);
4310    color("yellow",0.7) cuboid(size=size);
4311    vpr = [55,0,25];
4312    color("black")
4313    if (is_def(toplabel))
4314      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
4315}
4316
4317module _show_cube_faces(faces, size=20, toplabel,botlabel) {
4318   color("red")
4319     for(f=faces){
4320          move(f*size/2) rot(from=UP,to=f)
4321             cuboid([size,size,.1]);
4322     }
4323   vpr = [55,0,25];
4324   color("black"){
4325   if (is_def(toplabel))
4326     for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
4327   if (is_def(botlabel))
4328     for(h=idx(botlabel)) down(26+6*h)rot(vpr) text3d(botlabel[h],size=3.3,h=.1,orient=UP,anchor=FRONT);
4329   }
4330   color("yellow",0.7) cuboid(size=size);
4331}
4332
4333// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap